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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  28. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  29. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  30. package/src/lib/components/ai-logic.component.js +5 -3
  31. package/src/lib/components/ai-logic.component.js.map +1 -1
  32. package/src/lib/components/ai-media-create.component.js +88 -16
  33. package/src/lib/components/ai-media-create.component.js.map +1 -1
  34. package/src/lib/components/ai-model-selector.component.js +8 -0
  35. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  36. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  37. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  38. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  39. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  40. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  41. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  42. package/src/lib/components/ai-revise-automation.component.js +1 -1
  43. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  44. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  45. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  46. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  47. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  48. package/src/lib/components/ai-site-build-page.component.js +7 -3
  49. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  50. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  51. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-card.component.js +62 -29
  53. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  54. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  55. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  56. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  57. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  58. package/src/lib/components/ai-usage-strip.component.js +13 -2
  59. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +20 -3
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  65. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  66. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  67. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  68. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  69. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  70. package/src/lib/components/host-ai-allotments.component.js +2 -2
  71. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  72. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  73. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  74. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  75. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  76. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  77. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  78. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  79. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  80. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-language.js +4 -1
  82. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  83. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +12 -0
  84. package/src/lib/jobs/ai-job-layout-site-pages.js +20 -0
  85. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  86. package/src/lib/jobs/ai-job-page-language.d.ts +10 -1
  87. package/src/lib/jobs/ai-job-page-language.js +41 -8
  88. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  89. package/src/lib/jobs/ai-job-page-step.js +14 -3
  90. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  91. package/src/lib/jobs/ai-job-site-content.d.ts +75 -7
  92. package/src/lib/jobs/ai-job-site-content.js +205 -32
  93. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  94. package/src/lib/jobs/ai-job-site-step.d.ts +8 -0
  95. package/src/lib/jobs/ai-job-site-step.js +61 -12
  96. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-compiler.d.ts +8 -0
  98. package/src/lib/layout-language/ai-layout-compiler.js +308 -10
  99. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  100. package/src/lib/layout-language/ai-layout-design.d.ts +7 -0
  101. package/src/lib/layout-language/ai-layout-design.js +23 -11
  102. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  103. package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
  104. package/src/lib/layout-language/ai-layout-frame.js +12 -3
  105. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  106. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  107. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  108. package/src/lib/layout-language/ai-layout-links.d.ts +26 -1
  109. package/src/lib/layout-language/ai-layout-links.js +24 -4
  110. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  111. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  112. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  113. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  114. package/src/lib/model/ai-site-build-progress.js +27 -12
  115. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  116. package/src/lib/model/ai-site-job.d.ts +33 -8
  117. package/src/lib/model/ai-site-job.js +38 -18
  118. package/src/lib/model/ai-site-job.js.map +1 -1
  119. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  120. package/src/lib/model/ai-site-kinds.js +731 -117
  121. package/src/lib/model/ai-site-kinds.js.map +1 -1
  122. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  123. package/src/lib/model/ai-site-start-seo.js +132 -11
  124. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  125. package/src/lib/model/ai-site-start.d.ts +2 -2
  126. package/src/lib/model/ai-site-start.js +3 -3
  127. package/src/lib/model/ai-site-start.js.map +1 -1
  128. package/src/lib/plugin.js +15 -3
  129. package/src/lib/plugin.js.map +1 -1
  130. package/src/lib/runtime/ai-palette.generated.js +13 -3
  131. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  132. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  133. package/src/lib/server/ai-admin-conversations.js +151 -9
  134. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  135. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  136. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  137. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -87,6 +87,13 @@ export interface AiLayoutDesignChoices {
87
87
  splitHeads: boolean;
88
88
  /** How work is laid out where the kind shows it as pictures. */
89
89
  pictures: 'mosaic' | 'even';
90
+ /**
91
+ * How a blog's own posts are listed where a page shows them (AGL-3676):
92
+ * `grid`, a cover over each post's date, title and excerpt in columns; or
93
+ * `ruled`, a list under thin rules, the date in its own column and a small
94
+ * cover beside each title — the two ways an editorial site lists writing.
95
+ */
96
+ writing: 'grid' | 'ruled';
90
97
  }
91
98
  type Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard';
92
99
  /** The family a kind's pages are drawn in. */
@@ -146,17 +146,21 @@ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
146
146
  const family = aiLayoutFamily(design.kind);
147
147
  const heroes = (_HEROES_design_kind = HEROES[design.kind]) != null ? _HEROES_design_kind : DEFAULT_HEROES;
148
148
  const hero = next() < 0.55 ? heroes[0] : pick(next, heroes);
149
+ const group = family === 'calm' ? pick(next, [
150
+ 'cards',
151
+ 'cards',
152
+ 'ruled'
153
+ ]) : pick(next, [
154
+ 'ruled',
155
+ 'cards',
156
+ 'ruled'
157
+ ]);
149
158
  return {
150
159
  hero,
151
- group: family === 'calm' ? pick(next, [
152
- 'cards',
153
- 'cards',
154
- 'ruled'
155
- ]) : pick(next, [
156
- 'ruled',
157
- 'cards',
158
- 'ruled'
159
- ]),
160
+ // A store's reasons to buy — the making, the shipping, the care — read
161
+ // as open ruled columns beside its product photos, never as a SaaS
162
+ // page's feature cards (AGL-3676).
163
+ group: family === 'retail' ? 'ruled' : group,
160
164
  steps: pick(next, [
161
165
  'timeline',
162
166
  'numbers',
@@ -173,13 +177,21 @@ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
173
177
  'mosaic',
174
178
  'mosaic',
175
179
  'even'
180
+ ]),
181
+ // Drawn last, so every choice above stays what it was for a seed.
182
+ writing: pick(next, [
183
+ 'grid',
184
+ 'ruled'
176
185
  ])
177
186
  };
178
187
  }
179
188
  const WORK = /\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\b/i;
180
189
  const WRITING = /\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\b/i;
181
190
  const DISHES = /\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\b/i;
182
- const PRODUCTS = /\b(bestseller|bestsellers|best sellers|collection|collections|product|products|shop|range|candle|candles|gift|gifts|set|sets|new arrivals|arrivals)\b/i;
191
+ // A store's own products are listed by its Product grid (AGL-3676), never drawn
192
+ // from words: what a store shows as pictures is how its range is grouped — its
193
+ // collections, gift sets, occasions and scents. "Candle care and burn notes" is no band of pictures.
194
+ const CATEGORIES = /\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\b/i;
183
195
  /**
184
196
  * How a section's group of cards is drawn, from what the section is about:
185
197
  * work in a gallery kind and products in a store are pictures, writing in a
@@ -190,7 +202,7 @@ const PRODUCTS = /\b(bestseller|bestsellers|best sellers|collection|collections|
190
202
  if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures';
191
203
  if (family === 'editorial' && WRITING.test(words)) return 'articles';
192
204
  if (family === 'hospitality' && DISHES.test(words)) return 'menu';
193
- if (family === 'retail' && PRODUCTS.test(words)) return 'pictures';
205
+ if (family === 'retail' && CATEGORIES.test(words)) return 'pictures';
194
206
  return choices.group;
195
207
  }
196
208
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.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\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it. The first option of each\n * list is the kind's best, so it is favored; the seed still turns it, so two\n * sites of one kind are drawn apart.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = next() < 0.55 ? (heroes[0] as AiLayoutHeroVariant) : pick(next, heroes)\n return {\n hero,\n group: family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const),\n steps: pick(next, ['timeline', 'numbers', 'timeline'] as const),\n features: true,\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\nconst PRODUCTS = /\\b(bestseller|bestsellers|best sellers|collection|collections|product|products|shop|range|candle|candles|gift|gifts|set|sets|new arrivals|arrivals)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && PRODUCTS.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","WORK","WRITING","DISHES","PRODUCTS","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.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\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n /**\n * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it. The first option of each\n * list is the kind's best, so it is favored; the seed still turns it, so two\n * sites of one kind are drawn apart.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = next() < 0.55 ? (heroes[0] as AiLayoutHeroVariant) : pick(next, heroes)\n const group = family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open ruled columns beside its product photos, never as a SaaS\n // page's feature cards (AGL-3676).\n group: family === 'retail' ? 'ruled' : group,\n steps: pick(next, ['timeline', 'numbers', 'timeline'] as const),\n features: true,\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && CATEGORIES.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
@@ -44,6 +44,12 @@ export interface AiLayoutFramePlan {
44
44
  * would read as a second loud band, so the footer takes the quiet one.
45
45
  */
46
46
  closesDark?: boolean;
47
+ /**
48
+ * Whether the site sells (AGL-3676): its header carries the store's cart
49
+ * button — the commerce plugin's Cart, which opens the cart in a drawer
50
+ * from every page — beside the navigation.
51
+ */
52
+ cart?: boolean;
47
53
  }
48
54
  export interface AiLayoutCompiledFrame {
49
55
  tree: AiLayoutRawTree;
@@ -17,8 +17,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { AI_ICON_LIBRARY } from "../runtime/ai-icon-library.js";
18
18
  import { AI_LAYOUT_MAX_IMAGES, bandSx, compileFlow, compileRow, destinationProps } from "./ai-layout-compiler.js";
19
19
  import { aiLayoutFitText } from "./ai-layout-copy.js";
20
+ import { AI_LAYOUT_CART_ELEMENT } from "./ai-layout-listings.js";
20
21
  import { AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
21
- import { aiLayoutResolveLink } from "./ai-layout-links.js";
22
+ import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
22
23
  import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
23
24
  /** The header's colors by band, as palette roles. */ function headerLook(band) {
24
25
  if (band === 'brand') return {
@@ -116,7 +117,8 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
116
117
  design: null,
117
118
  pictures: 0,
118
119
  features: 0,
119
- splitAt: -2
120
+ splitAt: -2,
121
+ quotesOnly: []
120
122
  };
121
123
  const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home';
122
124
  const header = (_frame_header = frame.header) != null ? _frame_header : {
@@ -193,7 +195,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
193
195
  what: `the button "${ctaLabel}" has nowhere to go; left out`
194
196
  });
195
197
  const ctaProps = ctaTo ? _extends({
196
- children: ctaLabel,
198
+ children: aiLayoutRenamedLabel(ctaLabel, ctaTo),
197
199
  variant: 'contained',
198
200
  color: scope.band === 'brand' ? 'secondary' : 'primary'
199
201
  }, destinationProps(ctaTo)) : null;
@@ -248,6 +250,12 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
248
250
  }, [
249
251
  nav
250
252
  ], 'navRoom') : nav;
253
+ // A store's cart, on every page, where a shopper looks for it (AGL-3676).
254
+ const cart = plan.cart ? tree.add(AI_LAYOUT_CART_ELEMENT, {
255
+ variant: 'button'
256
+ }, {
257
+ flexShrink: 0
258
+ }, null, 'cart') : null;
251
259
  // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.
252
260
  const row = tree.add('muiContainer', {
253
261
  maxWidth: 'lg'
@@ -259,6 +267,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
259
267
  brand,
260
268
  middle,
261
269
  cta,
270
+ cart,
262
271
  toggle,
263
272
  drawer
264
273
  ], 'headerRow');
@@ -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 * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n}\n\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 // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n 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 // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // 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 asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n 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","design","pictures","features","splitAt","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","href","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","overHero","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","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;AAqC5E,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;QA0BTP,eACFQ;IAzBb,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;QACjB,yDAAyD;QACzDkC,QAAQ;QACRC,UAAU;QACVC,UAAU;QACVC,SAAS,CAAC;IACZ;IACA,MAAMC,OAAOrD,gBAAgB8B,KAAKK,QAAQ,EAAE,cAAc;IAC1D,MAAMH,UAASR,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB;QAAE8B,QAAQ,EAAE;IAAC;IAC5C,MAAMjD,QAAO2B,eAAAA,OAAO3B,IAAI,YAAX2B,eAAe;IAC5B,MAAMuB,OAAOnD,WAAWC;IACxB,MAAMmD,QAAQxC,WACZC,MACA,UACAZ,SAAS,WAAWA,SAAS,SAAS,UAAU,SAChD;IAEF,MAAMoD,UAAU,CACdC,OACAC,MACAC;QAEA,MAAMC,KAAK5B,KAAK6B,GAAG,CACjB,iBACA;YACEC,UACE/D,gBAAgB0D,MAAMM,KAAK,EAAE,YAAYN,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG;WAE5DP,MAAMQ,IAAI,GAAG;YAAEA,MAAMR,MAAMQ,IAAI;QAAC,IAAI;YAAEC,UAAUT,MAAMG,EAAE;QAAC;YAC7DO,UAAU;YACV7D,OAAO;YAET;YAAE8D,gBAAgB;QAAO,GACzB,MACAV;QAEF,IAAIC,cAAc3B,KAAKqC,KAAK,CAACT,GAAG,CAACD,YAAY,GAAGA;QAChD,OAAOC;IACT;IACA,MAAMU,QAAQzC,KAAK0C,QAAQ,CAACC,MAAM,CAAC,CAACf,QAAUA,MAAMM,KAAK,CAACU,IAAI;IAC9D,MAAMC,cAAc3C,OAAO4C,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,MAAMzC,QAAQd,KAAKwD,MAAM,GACrBrD,KAAK6B,GAAG,CACN,iBACA;QACEC,UAAUV,KAAKY,KAAK,CAAC,GAAG;QACxBE,UAAUrC,KAAKwD,MAAM;QACrBlB,UAAU;QACV7D,OAAO;IACT,GACAuE,SACA,MACA,WAEF7C,KAAK6B,GAAG,CACN,iBACA;QAAEC,UAAUV;QAAMkC,SAAS;QAAMC,WAAW;IAAI,GAChDV,SACA,MACA;IAEN,MAAMW,MAAMlB,MAAMM,MAAM,GACpB5C,KAAK6B,GAAG,CACN,YACA;QACE0B,WAAW;QACXE,WAAW;QACXC,WAAW;QACXC,SAAS;QACTC,YAAY;IACd,GACA;QAAEC,SAAS;YAAEC,IAAI;YAAQC,IAAI;QAAO;IAAE,GACtCzB,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,aACpC,SAEF;IACJ,0EAA0E;IAC1E,MAAMwC,WAAWlE,OAAOsB,MAAM,CAAC6C,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK;IAC9D,KAAK,MAAMD,SAASpE,OAAOsB,MAAM,CAAE;QACjC,IAAI8C,UAAUF,UACZjF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;YAChBpF,IAAI;YACJqF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;QACjD;IACJ;IACA,MAAMG,WAAWN,WAAWlG,gBAAgBkG,SAAStF,IAAI,EAAE,WAAW;IACtE,MAAM6F,QACJP,YAAYM,WACRtG,oBAAoBgG,SAASQ,EAAE,EAAEF,UAAUhD,MAAM/B,IAAI,EAAER,KAAKc,OAAO,IACnE;IACN,IAAImE,YAAY,CAACO,OACfxF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;QAChBpF,IAAI;QACJqF,MAAM,CAAC,YAAY,EAAEC,SAAS,6BAA6B,CAAC;IAC9D;IACF,MAAMG,WAAWF,QACb;QACE1C,UAAUyC;QACVjB,SAAS;QACThF,OAAOiD,MAAMnD,IAAI,KAAK,UAAU,cAAc;OAC3CN,iBAAiB0G,UAEtB;IACJ,iEAAiE;IACjE,MAAMG,MAAMD,WACR1E,KAAK6B,GAAG,CACN,aACA6C,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,GACvB5C,KAAK6B,GAAG,CACN,aACA;QAAEoD,QAAQ;IAAQ,GAClB,MACA;QACEjF,KAAK6B,GAAG,CACN,YACA;YAAE0B,WAAW;YAAOE,WAAW;QAAO,GACtC;YACEI,SAAS;YACTqB,eAAe;YACfC,KAAK;YACLC,GAAG;YACHtC,YAAY;QACd,GACA;eACKR,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,YAAYqD;YACnDJ,WACI1E,KAAK6B,GAAG,CACN,aACA,aAAK6C;gBAAUW,WAAW;gBAC1B;gBAAEC,IAAI;YAAE,GACR,MACA,aAEF;SACL,EACD;KAEH,EACD,YAEF;IACJ,MAAMC,SAASP,SACXhF,KAAK6B,GAAG,CACN,mBACA;QAAE4B,WAAW;IAAgB,GAC7B;QAAEI,SAAS;YAAEC,IAAI;YAAeC,IAAI;QAAO;QAAGc,YAAY;IAAE,GAC5D,MACA,gBAEF;IACJ,MAAMW,SACJhC,OAAOd,cACH1C,KAAK6B,GAAG,CACN,UACA,MACA;QAAEuB,UAAU;QAAGS,SAAS;QAAQ4B,gBAAgB;IAAS,GACzD;QAACjC;KAAI,EACL,aAEFA;IACN,6FAA6F;IAC7F,MAAMkC,MAAM1F,KAAK6B,GAAG,CAClB,gBACA;QAAE8D,UAAU;IAAK,GACjB;QAAE9B,SAAS;QAAQD,YAAY;QAAUgC,WAAW;IAAE,GACtD;QAACjF;QAAO6E;QAAQb;QAAKY;QAAQP;KAAO,EACpC;IAEF,MAAMa,UAAU7F,KAAK6B,GAAG,CACtB,cACA;QAAEiE,gBAAgB;IAAK,GACvB;QAAEC,WAAW;YAAEjC,IAAI;YAAIC,IAAI;QAAG;IAAE,GAChC;QAAC2B;KAAI,EACL;IAEF,MAAMM,MAAMhG,KAAK6B,GAAG,CAClB,aACA,aACKP,KAAKjD,KAAK;QACb4H,UAAU;QACV,mFAAmF;QACnFC,UAAU;QACV3C,WAAW;QACXE,WAAW;QAEbnC,KAAK/C,EAAE,EACP;QAACsH;KAAQ,EACT;IAEF,wEAAwE;IACxE,4BAA4B;IAC5B,MAAMM,OAAOnG,KAAK6B,GAAG,CACnB,cACA;QAAE0B,WAAW;IAAO,GACpB;QAAEH,UAAU;IAAE,GACd,MACA;IAEF,MAAMgD,SAASC,cAAcrH,MAAMO,MAAM6G,MAAM,EAAEvG,MAAMuB;IACvD,MAAMkF,SAAStG,KAAK6B,GAAG,CACrB,YACA,MACA;QAAEkE,WAAW;IAAQ,GACrB;QAACC;QAAKG;QAAMC;KAAO,EACnB;IAEFpG,KAAKuG,KAAK,CAACD,QAAQ,IAAIxH,IAAI;QAACqH;KAAK;IACjC,MAAMK,SAASxG,KAAK6B,GAAG,CAAC,OAAO,MAAM,MAAM;QAACyE;KAAO,EAAE;IACrD,OAAO;QAAEtG,MAAM;YAAEwG;YAAQnE,OAAOrC,KAAKqC,KAAK;QAAC;QAAG/B,SAAStB,KAAKsB,OAAO;IAAC;AACtE;AAEA,mDAAmD,GACnD,MAAMyE,yBAAyB;IAC7BnD,IAAI;IACJR,MAAM;IACNqF,SAAS;IACTC,SAAS;QAAEC,OAAO;QAAgBC,WAAW;IAAK;IAClDC,OAAO;QAAC;YAAEC,MAAM;QAAc;KAAE;AAClC;AAEA;;;;CAIC,GACD,SAAST,cACPrH,IAAe,EACfoH,MAA8B,EAC9BvG,IAAuB,EACvBuB,IAAY;;IAEZ,MAAMpB,OAAOhB,KAAKgB,IAAI;IACtB,MAAM+G,gBAAQX,0BAAAA,OAAQhI,IAAI,mBAAI;IAC9B,MAAMA,OAAOyB,KAAKmH,UAAU,IAAKD,CAAAA,UAAU,WAAWA,UAAU,MAAK,IAAK,SAASA;IACnF,IAAI3I,SAAS2I,OAAO/H,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;QAAEpF,IAAI;QAAUqF,MAAM,CAAC,EAAE,EAAEyC,MAAM,4CAA4C,CAAC;IAAC;IACrH,MAAMxF,QAAQxC,WAAWC,MAAM,UAAUZ,MAAMgI,CAAAA,0BAAAA,OAAQzD,KAAK,MAAK;IACjE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAMsE,WAAWpH,KAAK0C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;YAC7CxB,OAAOwB,MAAMM,KAAK;YAClBpD,MAAM;YACN8F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;QACxB,CAAA;IACA,IAAIsF,aAAa;IACjB,MAAM7F,SAAS,UAAC+E,0BAAAA,OAAQ/E,MAAM,oBAAI,EAAE,EAAE8F,OAAO,CAAC,CAACC;YAmB1CjD;YAXWiD;QAPd,uEAAuE;QACvE,uDAAuD;QACvD,IAAI1I,oBAAoB0I,MAAMzI,IAAI,GAAG;YACnCK,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAAEpF,IAAI;gBAAUqF,MAAM;YAA8C;YACtF,OAAO,EAAE;QACX;QACA,MAAM+C,SAAS,CAACC,OAA0C5I,oBAAoB4I,KAAKrH,KAAK,KAAKvB,oBAAoB4I,KAAK3I,IAAI;QAC1H,MAAMwF,QAAQiD,EAAAA,eAAAA,MAAMG,KAAK,qBAAXH,aAAaI,IAAI,CAACH,WAAU,aAAKD;YAAOG,OAAOH,MAAMG,KAAK,CAAC/E,MAAM,CAAC,CAAC8E,OAAS,CAACD,OAAOC;aAAWF;QAC7G,IAAIjD,UAAUiD,OAAOpI,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;YAAEpF,IAAI;YAAUqF,MAAM;QAAwD;QACrH,IAAIzF,kBAAkB4I,GAAG,CAACtD,MAAMC,IAAI,GAAG;YACrCpF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAChBpF,IAAI;gBACJqF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;YACjD;YACA,OAAO,EAAE;QACX;QACA,MAAMsD,YACJvD,MAAMC,IAAI,KAAK,UACf,EAACD,eAAAA,MAAMoD,KAAK,YAAXpD,eAAe,EAAE,EAAEqD,IAAI,CAAC,CAACF;gBAAuBA;mBAAd,SAAS1I,IAAI,EAAC0I,WAAAA,KAAK7C,EAAE,YAAP6C,WAAW;;QAC9D,IAAI,CAACI,WAAW,OAAO;YAACvD;SAAM;QAC9B,IAAI+C,cAAc,CAACD,SAASrE,MAAM,EAAE;YAClC5D,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAAEpF,IAAI;gBAAUqF,MAAM;YAAwC;YAChF,OAAO,EAAE;QACX;QACA4C,aAAa;QACb,OAAO;YAAC,aAAK/C;gBAAOoD,OAAON;;SAAW;IACxC;IACA,MAAMU,OAAOvB,CAAAA,0BAAAA,OAAQuB,IAAI,KAAIvB,OAAOuB,IAAI,CAAC/E,MAAM,IAAI,IAAIwD,OAAOuB,IAAI,GAAG;IACrE,MAAMC,UAA6BD,OAAOA,KAAK3D,GAAG,CAAC,IAAM,EAAE,IAAI;QAAC,EAAE;KAAC;IACnE,KAAK,MAAMG,SAAS9C,OAClBuG,OAAO,CACLD,QAAQxD,MAAM0D,GAAG,KAAKC,YAAYC,KAAKC,GAAG,CAAC7D,MAAM0D,GAAG,EAAEF,KAAK/E,MAAM,GAAG,KAAK,EAC1E,CAACyB,IAAI,CAACF;IACT,MAAM8D,UAAUN,OAAO;WAAIA;KAAK,GAAG;QAAC;KAAE;IACtC,IAAI,CAACT,cAAcrH,KAAK0C,QAAQ,CAACK,MAAM,EAAE;QACvCgF,QAAQvD,IAAI,CAAC;YACX;gBACED,MAAM;gBACNmD,OAAO1H,KAAK0C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;wBACnCxB,OAAOwB,MAAMM,KAAK;wBAClBpD,MAAM;wBACN8F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;oBACxB,CAAA;YACF;SACD;QACDqG,QAAQ5D,IAAI,CAAC;IACf;IACA,MAAM6D,SAASN,QACZ5D,GAAG,CAAC,CAACsC,QAAQL;YAAgCgC;eAAlB;YAAE3B;YAAQ6B,MAAM,GAAEF,oBAAAA,OAAO,CAAChC,SAAS,YAAjBgC,oBAAqB;QAAE;OACpEzF,MAAM,CAAC,CAACf,QAAUA,MAAM6E,MAAM,CAAC1D,MAAM;IACxC,MAAMwF,MACJF,OAAOtF,MAAM,GAAG,IACZ/E,WAAW0D,OAAO2G,UAClBA,OAAOtF,MAAM,KAAK,IAChBhF,YAAY2D,OAAO2G,MAAM,CAAC,EAAE,CAAC5B,MAAM,EAAE,UACrC;IACR,MAAM+B,YAAYrI,KAAK6B,GAAG,CACxB,iBACA;QAAEC,UAAU,CAAC,EAAE,EAAEV,MAAM;QAAEkC,SAAS;QAASC,WAAW;IAAI,GAC1DhC,MAAMnD,IAAI,KAAK,UAAU,OAAO;QAAEE,OAAO;IAAiB,GAC1D,MACA;IAEF,MAAMgK,SAAStI,KAAK6B,GAAG,CACrB,UACA,MACA;QAAE0G,IAAI;QAAGC,WAAW;QAAGC,aAAa;IAAU,GAC9C;QAACJ;KAAU,EACX;IAEF,MAAMK,QAAQ1I,KAAK6B,GAAG,CACpB,YACA;QAAE8B,SAAS;IAAI,GACf,MACA;QAACyE;QAAKE;KAAO,EACb;IAEF,MAAMK,YAAY3I,KAAK6B,GAAG,CACxB,gBACA;QAAE8D,UAAU;IAAK,GACjB;QAAEiD,IAAI;YAAE9E,IAAI;YAAGC,IAAI;QAAE;IAAE,GACvB;QAAC2E;KAAM,EACP;IAEF,OAAO1I,KAAK6B,GAAG,CACb,WACA;QACEgH,SAAS;QACTpF,WAAW;OACPlC,MAAMnD,IAAI,KAAK,SAAS;QAAE0K,aAAa;IAAO,IAAI,CAAC,IAEzDvH,MAAMnD,IAAI,KAAK,SACX;QAAEK,SAAS;QAAoB+J,WAAW;QAAGC,aAAa;IAAU,IACpE9K,OAAO4D,MAAMnD,IAAI,GACrB;QAACuK;KAAU,EACX;AAEJ"}
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 { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n}\n\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 // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n 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: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A 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 // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // 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, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // 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 asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","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","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","href","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","cart","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","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,SAASC,sBAAsB,QAAQ,0BAAsB;AAC7D,SACEC,mBAAmB,QAKd,0BAAsB;AAC7B,SACEC,oBAAoB,EACpBC,mBAAmB,QAGd,uBAAmB;AAC1B,SAASC,mBAAmB,QAA8B,sBAAkB;AA2C5E,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,WAAWvB;QACXwB,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;QA2BTP,eACFQ;IA1Bb,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,QAAQjD;QACRkD,QAAQ;YAAEC,OAAO;YAAGC,MAAM;QAAE;QAC5BjB,aAAa;QACbkB,UAAUtD,gBAAgBuD,OAAO;QACjCC,aAAa,IAAIjC;QACjB,yDAAyD;QACzDkC,QAAQ;QACRC,UAAU;QACVC,UAAU;QACVC,SAAS,CAAC;QACVC,YAAY,EAAE;IAChB;IACA,MAAMC,OAAOxD,gBAAgBgC,KAAKK,QAAQ,EAAE,cAAc;IAC1D,MAAMH,UAASR,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB;QAAE+B,QAAQ,EAAE;IAAC;IAC5C,MAAMlD,QAAO2B,eAAAA,OAAO3B,IAAI,YAAX2B,eAAe;IAC5B,MAAMwB,OAAOpD,WAAWC;IACxB,MAAMoD,QAAQzC,WACZC,MACA,UACAZ,SAAS,WAAWA,SAAS,SAAS,UAAU,SAChD;IAEF,MAAMqD,UAAU,CACdC,OACAC,MACAC;QAEA,MAAMC,KAAK7B,KAAK8B,GAAG,CACjB,iBACA;YACEC,UACElE,gBAAgB6D,MAAMM,KAAK,EAAE,YAAYN,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG;WAE5DP,MAAMQ,IAAI,GAAG;YAAEA,MAAMR,MAAMQ,IAAI;QAAC,IAAI;YAAEC,UAAUT,MAAMG,EAAE;QAAC;YAC7DO,UAAU;YACV9D,OAAO;YAET;YAAE+D,gBAAgB;QAAO,GACzB,MACAV;QAEF,IAAIC,cAAc5B,KAAKsC,KAAK,CAACT,GAAG,CAACD,YAAY,GAAGA;QAChD,OAAOC;IACT;IACA,MAAMU,QAAQ1C,KAAK2C,QAAQ,CAACC,MAAM,CAAC,CAACf,QAAUA,MAAMM,KAAK,CAACU,IAAI;IAC9D,MAAMC,cAAc5C,OAAO6C,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,MAAM1C,QAAQd,KAAKyD,MAAM,GACrBtD,KAAK8B,GAAG,CACN,iBACA;QACEC,UAAUV,KAAKY,KAAK,CAAC,GAAG;QACxBE,UAAUtC,KAAKyD,MAAM;QACrBlB,UAAU;QACV9D,OAAO;IACT,GACAwE,SACA,MACA,WAEF9C,KAAK8B,GAAG,CACN,iBACA;QAAEC,UAAUV;QAAMkC,SAAS;QAAMC,WAAW;IAAI,GAChDV,SACA,MACA;IAEN,MAAMW,MAAMlB,MAAMM,MAAM,GACpB7C,KAAK8B,GAAG,CACN,YACA;QACE0B,WAAW;QACXE,WAAW;QACXC,WAAW;QACXC,SAAS;QACTC,YAAY;IACd,GACA;QAAEC,SAAS;YAAEC,IAAI;YAAQC,IAAI;QAAO;IAAE,GACtCzB,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,aACpC,SAEF;IACJ,0EAA0E;IAC1E,MAAMwC,WAAWnE,OAAOuB,MAAM,CAAC6C,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK;IAC9D,KAAK,MAAMD,SAASrE,OAAOuB,MAAM,CAAE;QACjC,IAAI8C,UAAUF,UACZlF,KAAKsB,OAAO,CAACgE,IAAI,CAAC;YAChBrF,IAAI;YACJsF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;QACjD;IACJ;IACA,MAAMG,WAAWN,WAAWrG,gBAAgBqG,SAASvF,IAAI,EAAE,WAAW;IACtE,MAAM8F,QACJP,YAAYM,WACRvG,oBAAoBiG,SAASQ,EAAE,EAAEF,UAAUhD,MAAMhC,IAAI,EAAER,KAAKc,OAAO,IACnE;IACN,IAAIoE,YAAY,CAACO,OACfzF,KAAKsB,OAAO,CAACgE,IAAI,CAAC;QAChBrF,IAAI;QACJsF,MAAM,CAAC,YAAY,EAAEC,SAAS,6BAA6B,CAAC;IAC9D;IACF,MAAMG,WAAWF,QACb;QACE1C,UAAU/D,qBAAqBwG,UAAUC;QACzClB,SAAS;QACTjF,OAAOkD,MAAMpD,IAAI,KAAK,UAAU,cAAc;OAC3CR,iBAAiB6G,UAEtB;IACJ,iEAAiE;IACjE,MAAMG,MAAMD,WACR3E,KAAK8B,GAAG,CACN,aACA6C,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,GACvB7C,KAAK8B,GAAG,CACN,aACA;QAAEoD,QAAQ;IAAQ,GAClB,MACA;QACElF,KAAK8B,GAAG,CACN,YACA;YAAE0B,WAAW;YAAOE,WAAW;QAAO,GACtC;YACEI,SAAS;YACTqB,eAAe;YACfC,KAAK;YACLC,GAAG;YACHtC,YAAY;QACd,GACA;eACKR,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,YAAYqD;YACnDJ,WACI3E,KAAK8B,GAAG,CACN,aACA,aAAK6C;gBAAUW,WAAW;gBAC1B;gBAAEC,IAAI;YAAE,GACR,MACA,aAEF;SACL,EACD;KAEH,EACD,YAEF;IACJ,MAAMC,SAASP,SACXjF,KAAK8B,GAAG,CACN,mBACA;QAAE4B,WAAW;IAAgB,GAC7B;QAAEI,SAAS;YAAEC,IAAI;YAAeC,IAAI;QAAO;QAAGc,YAAY;IAAE,GAC5D,MACA,gBAEF;IACJ,MAAMW,SACJhC,OAAOd,cACH3C,KAAK8B,GAAG,CACN,UACA,MACA;QAAEuB,UAAU;QAAGS,SAAS;QAAQ4B,gBAAgB;IAAS,GACzD;QAACjC;KAAI,EACL,aAEFA;IACN,0EAA0E;IAC1E,MAAMkC,OAAO9F,KAAK8F,IAAI,GAClB3F,KAAK8B,GAAG,CAAChE,wBAAwB;QAAEyF,SAAS;IAAS,GAAG;QAAEuB,YAAY;IAAE,GAAG,MAAM,UACjF;IACJ,6FAA6F;IAC7F,MAAMc,MAAM5F,KAAK8B,GAAG,CAClB,gBACA;QAAE+D,UAAU;IAAK,GACjB;QAAE/B,SAAS;QAAQD,YAAY;QAAUiC,WAAW;IAAE,GACtD;QAACnF;QAAO8E;QAAQb;QAAKe;QAAMH;QAAQP;KAAO,EAC1C;IAEF,MAAMc,UAAU/F,KAAK8B,GAAG,CACtB,cACA;QAAEkE,gBAAgB;IAAK,GACvB;QAAEC,WAAW;YAAElC,IAAI;YAAIC,IAAI;QAAG;IAAE,GAChC;QAAC4B;KAAI,EACL;IAEF,MAAMM,MAAMlG,KAAK8B,GAAG,CAClB,aACA,aACKP,KAAKlD,KAAK;QACb8H,UAAU;QACV,mFAAmF;QACnFC,UAAU;QACV5C,WAAW;QACXE,WAAW;QAEbnC,KAAKhD,EAAE,EACP;QAACwH;KAAQ,EACT;IAEF,wEAAwE;IACxE,4BAA4B;IAC5B,MAAMM,OAAOrG,KAAK8B,GAAG,CACnB,cACA;QAAE0B,WAAW;IAAO,GACpB;QAAEH,UAAU;IAAE,GACd,MACA;IAEF,MAAMiD,SAASC,cAAcvH,MAAMO,MAAM+G,MAAM,EAAEzG,MAAMwB;IACvD,MAAMmF,SAASxG,KAAK8B,GAAG,CACrB,YACA,MACA;QAAEmE,WAAW;IAAQ,GACrB;QAACC;QAAKG;QAAMC;KAAO,EACnB;IAEFtG,KAAKyG,KAAK,CAACD,QAAQ,IAAI1H,IAAI;QAACuH;KAAK;IACjC,MAAMK,SAAS1G,KAAK8B,GAAG,CAAC,OAAO,MAAM,MAAM;QAAC0E;KAAO,EAAE;IACrD,OAAO;QAAExG,MAAM;YAAE0G;YAAQpE,OAAOtC,KAAKsC,KAAK;QAAC;QAAGhC,SAAStB,KAAKsB,OAAO;IAAC;AACtE;AAEA,mDAAmD,GACnD,MAAM0E,yBAAyB;IAC7BnD,IAAI;IACJR,MAAM;IACNsF,SAAS;IACTC,SAAS;QAAEC,OAAO;QAAgBC,WAAW;IAAK;IAClDC,OAAO;QAAC;YAAEC,MAAM;QAAc;KAAE;AAClC;AAEA;;;;CAIC,GACD,SAAST,cACPvH,IAAe,EACfsH,MAA8B,EAC9BzG,IAAuB,EACvBwB,IAAY;;IAEZ,MAAMrB,OAAOhB,KAAKgB,IAAI;IACtB,MAAMiH,gBAAQX,0BAAAA,OAAQlI,IAAI,mBAAI;IAC9B,MAAMA,OAAOyB,KAAKqH,UAAU,IAAKD,CAAAA,UAAU,WAAWA,UAAU,MAAK,IAAK,SAASA;IACnF,IAAI7I,SAAS6I,OAAOjI,KAAKsB,OAAO,CAACgE,IAAI,CAAC;QAAErF,IAAI;QAAUsF,MAAM,CAAC,EAAE,EAAE0C,MAAM,4CAA4C,CAAC;IAAC;IACrH,MAAMzF,QAAQzC,WAAWC,MAAM,UAAUZ,MAAMkI,CAAAA,0BAAAA,OAAQ1D,KAAK,MAAK;IACjE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAMuE,WAAWtH,KAAK2C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;YAC7CzB,OAAOyB,MAAMM,KAAK;YAClBrD,MAAM;YACN+F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;QACxB,CAAA;IACA,IAAIuF,aAAa;IACjB,MAAM9F,SAAS,UAACgF,0BAAAA,OAAQhF,MAAM,oBAAI,EAAE,EAAE+F,OAAO,CAAC,CAACC;YAmB1ClD;YAXWkD;QAPd,uEAAuE;QACvE,uDAAuD;QACvD,IAAI5I,oBAAoB4I,MAAM3I,IAAI,GAAG;YACnCK,KAAKsB,OAAO,CAACgE,IAAI,CAAC;gBAAErF,IAAI;gBAAUsF,MAAM;YAA8C;YACtF,OAAO,EAAE;QACX;QACA,MAAMgD,SAAS,CAACC,OAA0C9I,oBAAoB8I,KAAKvH,KAAK,KAAKvB,oBAAoB8I,KAAK7I,IAAI;QAC1H,MAAMyF,QAAQkD,EAAAA,eAAAA,MAAMG,KAAK,qBAAXH,aAAaI,IAAI,CAACH,WAAU,aAAKD;YAAOG,OAAOH,MAAMG,KAAK,CAAChF,MAAM,CAAC,CAAC+E,OAAS,CAACD,OAAOC;aAAWF;QAC7G,IAAIlD,UAAUkD,OAAOtI,KAAKsB,OAAO,CAACgE,IAAI,CAAC;YAAErF,IAAI;YAAUsF,MAAM;QAAwD;QACrH,IAAI1F,kBAAkB8I,GAAG,CAACvD,MAAMC,IAAI,GAAG;YACrCrF,KAAKsB,OAAO,CAACgE,IAAI,CAAC;gBAChBrF,IAAI;gBACJsF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;YACjD;YACA,OAAO,EAAE;QACX;QACA,MAAMuD,YACJxD,MAAMC,IAAI,KAAK,UACf,EAACD,eAAAA,MAAMqD,KAAK,YAAXrD,eAAe,EAAE,EAAEsD,IAAI,CAAC,CAACF;gBAAuBA;mBAAd,SAAS5I,IAAI,EAAC4I,WAAAA,KAAK9C,EAAE,YAAP8C,WAAW;;QAC9D,IAAI,CAACI,WAAW,OAAO;YAACxD;SAAM;QAC9B,IAAIgD,cAAc,CAACD,SAAStE,MAAM,EAAE;YAClC7D,KAAKsB,OAAO,CAACgE,IAAI,CAAC;gBAAErF,IAAI;gBAAUsF,MAAM;YAAwC;YAChF,OAAO,EAAE;QACX;QACA6C,aAAa;QACb,OAAO;YAAC,aAAKhD;gBAAOqD,OAAON;;SAAW;IACxC;IACA,MAAMU,OAAOvB,CAAAA,0BAAAA,OAAQuB,IAAI,KAAIvB,OAAOuB,IAAI,CAAChF,MAAM,IAAI,IAAIyD,OAAOuB,IAAI,GAAG;IACrE,MAAMC,UAA6BD,OAAOA,KAAK5D,GAAG,CAAC,IAAM,EAAE,IAAI;QAAC,EAAE;KAAC;IACnE,KAAK,MAAMG,SAAS9C,OAClBwG,OAAO,CACLD,QAAQzD,MAAM2D,GAAG,KAAKC,YAAYC,KAAKC,GAAG,CAAC9D,MAAM2D,GAAG,EAAEF,KAAKhF,MAAM,GAAG,KAAK,EAC1E,CAACyB,IAAI,CAACF;IACT,MAAM+D,UAAUN,OAAO;WAAIA;KAAK,GAAG;QAAC;KAAE;IACtC,IAAI,CAACT,cAAcvH,KAAK2C,QAAQ,CAACK,MAAM,EAAE;QACvCiF,QAAQxD,IAAI,CAAC;YACX;gBACED,MAAM;gBACNoD,OAAO5H,KAAK2C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;wBACnCzB,OAAOyB,MAAMM,KAAK;wBAClBrD,MAAM;wBACN+F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;oBACxB,CAAA;YACF;SACD;QACDsG,QAAQ7D,IAAI,CAAC;IACf;IACA,MAAM8D,SAASN,QACZ7D,GAAG,CAAC,CAACuC,QAAQL;YAAgCgC;eAAlB;YAAE3B;YAAQ6B,MAAM,GAAEF,oBAAAA,OAAO,CAAChC,SAAS,YAAjBgC,oBAAqB;QAAE;OACpE1F,MAAM,CAAC,CAACf,QAAUA,MAAM8E,MAAM,CAAC3D,MAAM;IACxC,MAAMyF,MACJF,OAAOvF,MAAM,GAAG,IACZlF,WAAW6D,OAAO4G,UAClBA,OAAOvF,MAAM,KAAK,IAChBnF,YAAY8D,OAAO4G,MAAM,CAAC,EAAE,CAAC5B,MAAM,EAAE,UACrC;IACR,MAAM+B,YAAYvI,KAAK8B,GAAG,CACxB,iBACA;QAAEC,UAAU,CAAC,EAAE,EAAEV,MAAM;QAAEkC,SAAS;QAASC,WAAW;IAAI,GAC1DhC,MAAMpD,IAAI,KAAK,UAAU,OAAO;QAAEE,OAAO;IAAiB,GAC1D,MACA;IAEF,MAAMkK,SAASxI,KAAK8B,GAAG,CACrB,UACA,MACA;QAAE2G,IAAI;QAAGC,WAAW;QAAGC,aAAa;IAAU,GAC9C;QAACJ;KAAU,EACX;IAEF,MAAMK,QAAQ5I,KAAK8B,GAAG,CACpB,YACA;QAAE8B,SAAS;IAAI,GACf,MACA;QAAC0E;QAAKE;KAAO,EACb;IAEF,MAAMK,YAAY7I,KAAK8B,GAAG,CACxB,gBACA;QAAE+D,UAAU;IAAK,GACjB;QAAEiD,IAAI;YAAE/E,IAAI;YAAGC,IAAI;QAAE;IAAE,GACvB;QAAC4E;KAAM,EACP;IAEF,OAAO5I,KAAK8B,GAAG,CACb,WACA;QACEiH,SAAS;QACTrF,WAAW;OACPlC,MAAMpD,IAAI,KAAK,SAAS;QAAE4K,aAAa;IAAO,IAAI,CAAC,IAEzDxH,MAAMpD,IAAI,KAAK,SACX;QAAEK,SAAS;QAAoBiK,WAAW;QAAGC,aAAa;IAAU,IACpElL,OAAO+D,MAAMpD,IAAI,GACrB;QAACyK;KAAU,EACX;AAEJ"}
@@ -32,7 +32,11 @@ import { aiStoredScrollTargets } from "../runtime/ai-page-links.js";
32
32
  'layoutSlot',
33
33
  'reusableInstance',
34
34
  'muiDrawerToggle',
35
- 'muiDrawer'
35
+ 'muiDrawer',
36
+ // The site's own records, listed by the elements that keep them (AGL-3676).
37
+ 'product-grid',
38
+ 'collectionEntries',
39
+ 'cart'
36
40
  ]);
37
41
  const HEADING_VARIANTS = new Set([
38
42
  'h1',
@@ -251,9 +255,21 @@ function refsIn(text, names) {
251
255
  ...new Set(found)
252
256
  ];
253
257
  }
258
+ /** An entry's own token, which a collection repeater fills per entry when the page renders. */ const ENTRY_TOKEN = /\{\{\s*entry\.[A-Za-z]+\s*\}\}/g;
259
+ /** Every node under a Collection Entries block: its template, repeated once per entry (AGL-3676). */ function repeatedByEntries(nodes) {
260
+ const repeated = new Set();
261
+ for (const [id, node] of Object.entries(nodes)){
262
+ if (node.componentId !== 'collectionEntries') continue;
263
+ for (const each of subtree(nodes, id))if (each !== id) repeated.add(each);
264
+ }
265
+ return repeated;
266
+ }
254
267
  function refsOf(nodes, names) {
268
+ // A post's card names its post by the entry's tokens, which the page fills
269
+ // per post: words a visitor reads as the post's own, never a reference.
270
+ const repeated = repeatedByEntries(nodes);
255
271
  return Object.entries(nodes).flatMap(([id, node])=>{
256
- const texts = shownTexts(node).map((slot)=>slot.get());
272
+ const texts = shownTexts(node).map((slot)=>repeated.has(id) ? slot.get().replace(ENTRY_TOKEN, '') : slot.get());
257
273
  const refs = [
258
274
  ...new Set(texts.flatMap((text)=>refsIn(text, names)))
259
275
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-gaps.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiBracketedFacts, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { aiStoredScrollTargets } from '../runtime/ai-page-links'\n\n/**\n * No gap on a published page (AGL-3660). A guided start publishes what it\n * builds, so a fact the brief never gave — a phone number, an address, the\n * opening hours — is left out of a page or a frame built in the layout\n * language, never written as the bracketed gap (\"[phone number]\") a draft a\n * person edits first may carry. An answer holding one is asked again; the\n * last answer a generation takes has each one taken out instead: the line\n * that carries it, the row or card it leaves with nothing to say, and a\n * section left with only its heading.\n */\n\ninterface GapNode {\n componentId: string\n props?: Record<string, unknown>\n nodes?: string[]\n}\n\ntype GapNodes = Record<string, GapNode>\n\n/** Props that hold no words a visitor reads: an icon's drawing is path data. */\nconst NOT_WORDS = new Set(['iconPath', 'iconId'])\n\n/** The props a node's own words are in. */\nconst WORD_PROPS = ['children', 'primary', 'secondary', 'label'] as const\n\n/** Elements that say something with no words of their own. */\nconst SPEAKS_ALONE = new Set(['image', 'form', 'layoutSlot', 'reusableInstance', 'muiDrawerToggle', 'muiDrawer'])\n\nconst HEADING_VARIANTS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'overline'])\n\n/** The violation code a gap is named by. */\nexport const AI_LAYOUT_GAP_CODE = 'layout-gap'\n\n/** Every string a value holds, however deep, its icon drawings aside. */\nfunction stringsOf(value: unknown, key = ''): string[] {\n if (NOT_WORDS.has(key)) return []\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.flatMap((entry) => stringsOf(entry))\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>).flatMap(([name, entry]) => stringsOf(entry, name))\n }\n return []\n}\n\n/** Each node whose words carry a gap, with the gaps it carries. */\nfunction gapsOf(nodes: GapNodes): Array<{ id: string; facts: string[]; text: string }> {\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = stringsOf(node.props ?? {})\n const facts = aiBracketedFacts(texts)\n return facts.length ? [{ id, facts, text: texts.filter((text) => aiBracketedFacts([text]).length).join(' ') }] : []\n })\n}\n\n/** The gaps a language-built page or frame carries, as the violation that asks again. */\nexport function aiLayoutGapViolations(nodes: NodesMap): AiDoctrineViolation[] {\n const found = gapsOf(nodes as unknown as GapNodes)\n if (!found.length) return []\n const facts = aiBracketedFacts(found.flatMap((entry) => entry.facts))\n return [\n {\n rule: 14,\n code: AI_LAYOUT_GAP_CODE,\n message: `${facts.join(', ')} ${facts.length === 1 ? 'is a gap' : 'are gaps'} for a fact the brief does not give, and this site is published exactly as written. Leave each fact out: write the sentence without it, and make no list line, card or contact row for it. Never write a gap in square brackets, and never invent the fact.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\nfunction parentsOf(nodes: GapNodes): Map<string, string> {\n const parents = new Map<string, string>()\n for (const [id, node] of Object.entries(nodes)) for (const child of node.nodes ?? []) parents.set(child, id)\n return parents\n}\n\nfunction subtree(nodes: GapNodes, id: string): string[] {\n const ids: string[] = []\n const stack = [id]\n while (stack.length) {\n const next = stack.pop() as string\n if (!nodes[next] || ids.includes(next)) continue\n ids.push(next)\n stack.push(...(nodes[next].nodes ?? []))\n }\n return ids\n}\n\nfunction wordsOf(node: GapNode): string {\n for (const prop of WORD_PROPS) {\n const value = node.props?.[prop]\n if (typeof value === 'string' && value.trim()) return value.trim()\n }\n return ''\n}\n\nfunction isHeading(node: GapNode): boolean {\n if (node.componentId !== 'muiTypography') return false\n return HEADING_VARIANTS.has(String(node.props?.['variant'] ?? '')) || /^h[1-6]$/.test(String(node.props?.['component'] ?? ''))\n}\n\n/** Whether anything under a node says something; with `beyondHeadings`, anything but a heading. */\nfunction speaks(nodes: GapNodes, id: string, beyondHeadings = false): boolean {\n return subtree(nodes, id).some((each) => {\n const node = nodes[each]\n const says = SPEAKS_ALONE.has(node.componentId) || !!wordsOf(node)\n return says && !(beyondHeadings && isHeading(node))\n })\n}\n\n/** A node's props, its component's prop values copied too, so a rewrite never reaches the input. */\nfunction copyProps(props: Record<string, unknown>): Record<string, unknown> {\n const values = props['propValues']\n return values && typeof values === 'object' && !Array.isArray(values)\n ? { ...props, propValues: { ...(values as Record<string, unknown>) } }\n : { ...props }\n}\n\nfunction cloneNodes(input: NodesMap): GapNodes {\n return Object.fromEntries(\n Object.entries(input as unknown as GapNodes).map(([id, node]) => [\n id,\n {\n ...node,\n ...(node.props ? { props: copyProps(node.props) } : {}),\n ...(node.nodes ? { nodes: [...node.nodes] } : {}),\n },\n ]),\n )\n}\n\n/**\n * Takes each found node out, and each element above it left with nothing to\n * say, up to its section; a section other than the page's first left with\n * only its heading goes too, with any link that scrolled to it.\n */\nfunction pruneFound(nodes: GapNodes, rootId: string, found: ReadonlyArray<{ id: string; text: string }>): string[] {\n const dropped: string[] = []\n const sections = new Set(nodes[rootId]?.nodes ?? [])\n const touched = new Set<string>()\n const remove = (id: string) => {\n const parent = parentsOf(nodes).get(id)\n for (const each of subtree(nodes, id)) delete nodes[each]\n if (parent && nodes[parent]) nodes[parent].nodes = (nodes[parent].nodes ?? []).filter((child) => child !== id)\n }\n /** Takes a node out, and each element above it it leaves with nothing to say, short of a section. */\n const prune = (id: string) => {\n const parents = parentsOf(nodes)\n let section: string | undefined = id\n while (section && !sections.has(section)) section = parents.get(section)\n if (section && section !== id) touched.add(section)\n let parent = parents.get(id)\n remove(id)\n while (parent && parent !== rootId && nodes[parent]) {\n if (sections.has(parent) || speaks(nodes, parent)) break\n const above = parentsOf(nodes).get(parent)\n remove(parent)\n parent = above\n }\n }\n for (const entry of found) {\n if (!nodes[entry.id]) continue\n dropped.push(entry.text)\n prune(entry.id)\n }\n const first = nodes[rootId]?.nodes?.[0]\n for (const section of touched) {\n if (section === first || !nodes[section] || speaks(nodes, section, true)) continue\n const label = String(nodes[section].props?.['ariaLabel'] ?? section)\n remove(section)\n dropped.push(`the section \"${label}\", left with only its heading`)\n for (const [id, node] of Object.entries(nodes)) {\n if (nodes[id] && aiStoredScrollTargets(node).includes(section)) {\n dropped.push(`\"${wordsOf(node)}\", a link to that section`)\n prune(id)\n }\n }\n }\n return dropped\n}\n\n/** The page or frame with every gap taken out, and what was taken. */\nexport function aiLayoutWithoutGaps(input: NodesMap, rootId: string): { nodes: NodesMap; dropped: string[] } {\n const found = gapsOf(input as unknown as GapNodes)\n if (!found.length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const dropped = pruneFound(nodes, rootId, found)\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n// ── Internal references ─────────────────────────────────────────────────\n\n/**\n * What a link may name, which never belongs in words a visitor reads: the\n * site's pages, with the title a reference to one is written as, and every\n * other id the page or frame was built against (its forms, components and\n * sections).\n */\nexport interface AiLayoutRefNames {\n pages: ReadonlyArray<{ id: string; label: string }>\n ids: readonly string[]\n}\n\n/** The violation code an internal reference in copy is named by. */\nexport const AI_LAYOUT_REF_CODE = 'layout-internal-ref'\n\n/** A link's own notation, or a template token other than the site's own (`{{host.…}}`). */\nconst REF = /\\b(?:page|new|form|component|screen):[A-Za-z0-9_-]+|\\{\\{(?!\\s*host\\.)[^{}]*\\}\\}/g\nconst PAGE_REF = /\\bpage:([A-Za-z0-9_-]+)/g\n\n/** The props whose words a visitor reads, and a component's prop values. */\nconst SHOWN_PROPS = [...WORD_PROPS, 'alt', 'title']\n\nfunction shownTexts(node: GapNode): Array<{ get: () => string; set: (value: string) => void }> {\n const props = node.props ?? {}\n const slots: Array<{ get: () => string; set: (value: string) => void }> = []\n for (const prop of SHOWN_PROPS) {\n if (typeof props[prop] === 'string') slots.push({ get: () => props[prop] as string, set: (value) => (props[prop] = value) })\n }\n const values = props['propValues']\n if (values && typeof values === 'object' && !Array.isArray(values)) {\n const record = values as Record<string, unknown>\n for (const name of Object.keys(record)) {\n if (typeof record[name] === 'string') slots.push({ get: () => record[name] as string, set: (value) => (record[name] = value) })\n }\n }\n return slots\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction refsIn(text: string, names: AiLayoutRefNames): string[] {\n const found = (text.match(REF) ?? []).map((ref) => ref.trim())\n for (const id of names.ids) {\n if (id.length >= 6 && new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(id)}($|[^A-Za-z0-9_-])`).test(text)) found.push(id)\n }\n return [...new Set(found)]\n}\n\nfunction refsOf(nodes: GapNodes, names: AiLayoutRefNames): Array<{ id: string; refs: string[]; text: string }> {\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = shownTexts(node).map((slot) => slot.get())\n const refs = [...new Set(texts.flatMap((text) => refsIn(text, names)))]\n return refs.length ? [{ id, refs, text: texts.filter((text) => refsIn(text, names).length).join(' ') }] : []\n })\n}\n\n/** Internal references in a language-built page's or frame's words, as the violation that asks again. */\nexport function aiLayoutRefViolations(nodes: NodesMap, names: AiLayoutRefNames): AiDoctrineViolation[] {\n const found = refsOf(nodes as unknown as GapNodes, names)\n if (!found.length) return []\n const refs = [...new Set(found.flatMap((entry) => entry.refs))].map((ref) => `\"${ref}\"`)\n return [\n {\n rule: 14,\n code: AI_LAYOUT_REF_CODE,\n message: `${refs.join(', ')} ${refs.length === 1 ? 'is a link reference' : 'are link references'} written into words a visitor reads. A reference goes only in a button's or a list item's to; in text, name the page by its title, or leave the reference out.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\n/**\n * The page or frame with no internal reference in its words: a page's\n * reference is written as that page's title, and a line that still names a\n * reference is taken out, as a gap is. Returns what was rewritten and taken.\n */\nexport function aiLayoutWithoutRefs(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n): { nodes: NodesMap; dropped: string[] } {\n if (!refsOf(input as unknown as GapNodes, names).length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const labels = new Map(names.pages.map((page) => [page.id, page.label]))\n const dropped: string[] = []\n for (const node of Object.values(nodes)) {\n for (const slot of shownTexts(node)) {\n const before = slot.get()\n const after = before.replace(PAGE_REF, (ref, id: string) => labels.get(id) ?? ref)\n if (after !== before) {\n slot.set(after)\n dropped.push(`\"${before}\" written as \"${after}\"`)\n }\n }\n }\n dropped.push(...pruneFound(nodes, rootId, refsOf(nodes, names)))\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n/**\n * A language-built page's or frame's words held to both (AGL-3660): before\n * the last answer, each gap and each internal reference is a violation that\n * asks again; in the last, they are taken out and the rest is kept.\n */\nexport function aiLayoutCopyCheck(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n last: boolean,\n): { nodes: NodesMap; dropped: string[]; violations: AiDoctrineViolation[] } {\n if (!last) {\n return { nodes: input, dropped: [], violations: [...aiLayoutRefViolations(input, names), ...aiLayoutGapViolations(input)] }\n }\n const refs = aiLayoutWithoutRefs(input, rootId, names)\n const gaps = aiLayoutWithoutGaps(refs.nodes, rootId)\n return { nodes: gaps.nodes, dropped: [...refs.dropped, ...gaps.dropped], violations: [] }\n}\n"],"names":["aiBracketedFacts","aiStoredScrollTargets","NOT_WORDS","Set","WORD_PROPS","SPEAKS_ALONE","HEADING_VARIANTS","AI_LAYOUT_GAP_CODE","stringsOf","value","key","has","Array","isArray","flatMap","entry","Object","entries","name","gapsOf","nodes","id","node","texts","props","facts","length","text","filter","join","aiLayoutGapViolations","found","rule","code","message","nodeIds","map","parentsOf","parents","Map","child","set","subtree","ids","stack","next","pop","includes","push","wordsOf","prop","trim","isHeading","componentId","String","test","speaks","beyondHeadings","some","each","says","copyProps","values","propValues","cloneNodes","input","fromEntries","pruneFound","rootId","dropped","sections","touched","remove","parent","get","prune","section","add","above","first","label","aiLayoutWithoutGaps","AI_LAYOUT_REF_CODE","REF","PAGE_REF","SHOWN_PROPS","shownTexts","slots","record","keys","escapeRegExp","replace","refsIn","names","match","ref","RegExp","refsOf","slot","refs","aiLayoutRefViolations","aiLayoutWithoutRefs","labels","pages","page","before","after","aiLayoutCopyCheck","last","violations","gaps"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-gaps.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiBracketedFacts, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { aiStoredScrollTargets } from '../runtime/ai-page-links'\n\n/**\n * No gap on a published page (AGL-3660). A guided start publishes what it\n * builds, so a fact the brief never gave — a phone number, an address, the\n * opening hours — is left out of a page or a frame built in the layout\n * language, never written as the bracketed gap (\"[phone number]\") a draft a\n * person edits first may carry. An answer holding one is asked again; the\n * last answer a generation takes has each one taken out instead: the line\n * that carries it, the row or card it leaves with nothing to say, and a\n * section left with only its heading.\n */\n\ninterface GapNode {\n componentId: string\n props?: Record<string, unknown>\n nodes?: string[]\n}\n\ntype GapNodes = Record<string, GapNode>\n\n/** Props that hold no words a visitor reads: an icon's drawing is path data. */\nconst NOT_WORDS = new Set(['iconPath', 'iconId'])\n\n/** The props a node's own words are in. */\nconst WORD_PROPS = ['children', 'primary', 'secondary', 'label'] as const\n\n/** Elements that say something with no words of their own. */\nconst SPEAKS_ALONE = new Set([\n 'image',\n 'form',\n 'layoutSlot',\n 'reusableInstance',\n 'muiDrawerToggle',\n 'muiDrawer',\n // The site's own records, listed by the elements that keep them (AGL-3676).\n 'product-grid',\n 'collectionEntries',\n 'cart',\n])\n\nconst HEADING_VARIANTS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'overline'])\n\n/** The violation code a gap is named by. */\nexport const AI_LAYOUT_GAP_CODE = 'layout-gap'\n\n/** Every string a value holds, however deep, its icon drawings aside. */\nfunction stringsOf(value: unknown, key = ''): string[] {\n if (NOT_WORDS.has(key)) return []\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.flatMap((entry) => stringsOf(entry))\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>).flatMap(([name, entry]) => stringsOf(entry, name))\n }\n return []\n}\n\n/** Each node whose words carry a gap, with the gaps it carries. */\nfunction gapsOf(nodes: GapNodes): Array<{ id: string; facts: string[]; text: string }> {\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = stringsOf(node.props ?? {})\n const facts = aiBracketedFacts(texts)\n return facts.length ? [{ id, facts, text: texts.filter((text) => aiBracketedFacts([text]).length).join(' ') }] : []\n })\n}\n\n/** The gaps a language-built page or frame carries, as the violation that asks again. */\nexport function aiLayoutGapViolations(nodes: NodesMap): AiDoctrineViolation[] {\n const found = gapsOf(nodes as unknown as GapNodes)\n if (!found.length) return []\n const facts = aiBracketedFacts(found.flatMap((entry) => entry.facts))\n return [\n {\n rule: 14,\n code: AI_LAYOUT_GAP_CODE,\n message: `${facts.join(', ')} ${facts.length === 1 ? 'is a gap' : 'are gaps'} for a fact the brief does not give, and this site is published exactly as written. Leave each fact out: write the sentence without it, and make no list line, card or contact row for it. Never write a gap in square brackets, and never invent the fact.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\nfunction parentsOf(nodes: GapNodes): Map<string, string> {\n const parents = new Map<string, string>()\n for (const [id, node] of Object.entries(nodes)) for (const child of node.nodes ?? []) parents.set(child, id)\n return parents\n}\n\nfunction subtree(nodes: GapNodes, id: string): string[] {\n const ids: string[] = []\n const stack = [id]\n while (stack.length) {\n const next = stack.pop() as string\n if (!nodes[next] || ids.includes(next)) continue\n ids.push(next)\n stack.push(...(nodes[next].nodes ?? []))\n }\n return ids\n}\n\nfunction wordsOf(node: GapNode): string {\n for (const prop of WORD_PROPS) {\n const value = node.props?.[prop]\n if (typeof value === 'string' && value.trim()) return value.trim()\n }\n return ''\n}\n\nfunction isHeading(node: GapNode): boolean {\n if (node.componentId !== 'muiTypography') return false\n return HEADING_VARIANTS.has(String(node.props?.['variant'] ?? '')) || /^h[1-6]$/.test(String(node.props?.['component'] ?? ''))\n}\n\n/** Whether anything under a node says something; with `beyondHeadings`, anything but a heading. */\nfunction speaks(nodes: GapNodes, id: string, beyondHeadings = false): boolean {\n return subtree(nodes, id).some((each) => {\n const node = nodes[each]\n const says = SPEAKS_ALONE.has(node.componentId) || !!wordsOf(node)\n return says && !(beyondHeadings && isHeading(node))\n })\n}\n\n/** A node's props, its component's prop values copied too, so a rewrite never reaches the input. */\nfunction copyProps(props: Record<string, unknown>): Record<string, unknown> {\n const values = props['propValues']\n return values && typeof values === 'object' && !Array.isArray(values)\n ? { ...props, propValues: { ...(values as Record<string, unknown>) } }\n : { ...props }\n}\n\nfunction cloneNodes(input: NodesMap): GapNodes {\n return Object.fromEntries(\n Object.entries(input as unknown as GapNodes).map(([id, node]) => [\n id,\n {\n ...node,\n ...(node.props ? { props: copyProps(node.props) } : {}),\n ...(node.nodes ? { nodes: [...node.nodes] } : {}),\n },\n ]),\n )\n}\n\n/**\n * Takes each found node out, and each element above it left with nothing to\n * say, up to its section; a section other than the page's first left with\n * only its heading goes too, with any link that scrolled to it.\n */\nfunction pruneFound(nodes: GapNodes, rootId: string, found: ReadonlyArray<{ id: string; text: string }>): string[] {\n const dropped: string[] = []\n const sections = new Set(nodes[rootId]?.nodes ?? [])\n const touched = new Set<string>()\n const remove = (id: string) => {\n const parent = parentsOf(nodes).get(id)\n for (const each of subtree(nodes, id)) delete nodes[each]\n if (parent && nodes[parent]) nodes[parent].nodes = (nodes[parent].nodes ?? []).filter((child) => child !== id)\n }\n /** Takes a node out, and each element above it it leaves with nothing to say, short of a section. */\n const prune = (id: string) => {\n const parents = parentsOf(nodes)\n let section: string | undefined = id\n while (section && !sections.has(section)) section = parents.get(section)\n if (section && section !== id) touched.add(section)\n let parent = parents.get(id)\n remove(id)\n while (parent && parent !== rootId && nodes[parent]) {\n if (sections.has(parent) || speaks(nodes, parent)) break\n const above = parentsOf(nodes).get(parent)\n remove(parent)\n parent = above\n }\n }\n for (const entry of found) {\n if (!nodes[entry.id]) continue\n dropped.push(entry.text)\n prune(entry.id)\n }\n const first = nodes[rootId]?.nodes?.[0]\n for (const section of touched) {\n if (section === first || !nodes[section] || speaks(nodes, section, true)) continue\n const label = String(nodes[section].props?.['ariaLabel'] ?? section)\n remove(section)\n dropped.push(`the section \"${label}\", left with only its heading`)\n for (const [id, node] of Object.entries(nodes)) {\n if (nodes[id] && aiStoredScrollTargets(node).includes(section)) {\n dropped.push(`\"${wordsOf(node)}\", a link to that section`)\n prune(id)\n }\n }\n }\n return dropped\n}\n\n/** The page or frame with every gap taken out, and what was taken. */\nexport function aiLayoutWithoutGaps(input: NodesMap, rootId: string): { nodes: NodesMap; dropped: string[] } {\n const found = gapsOf(input as unknown as GapNodes)\n if (!found.length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const dropped = pruneFound(nodes, rootId, found)\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n// ── Internal references ─────────────────────────────────────────────────\n\n/**\n * What a link may name, which never belongs in words a visitor reads: the\n * site's pages, with the title a reference to one is written as, and every\n * other id the page or frame was built against (its forms, components and\n * sections).\n */\nexport interface AiLayoutRefNames {\n pages: ReadonlyArray<{ id: string; label: string }>\n ids: readonly string[]\n}\n\n/** The violation code an internal reference in copy is named by. */\nexport const AI_LAYOUT_REF_CODE = 'layout-internal-ref'\n\n/** A link's own notation, or a template token other than the site's own (`{{host.…}}`). */\nconst REF = /\\b(?:page|new|form|component|screen):[A-Za-z0-9_-]+|\\{\\{(?!\\s*host\\.)[^{}]*\\}\\}/g\nconst PAGE_REF = /\\bpage:([A-Za-z0-9_-]+)/g\n\n/** The props whose words a visitor reads, and a component's prop values. */\nconst SHOWN_PROPS = [...WORD_PROPS, 'alt', 'title']\n\nfunction shownTexts(node: GapNode): Array<{ get: () => string; set: (value: string) => void }> {\n const props = node.props ?? {}\n const slots: Array<{ get: () => string; set: (value: string) => void }> = []\n for (const prop of SHOWN_PROPS) {\n if (typeof props[prop] === 'string') slots.push({ get: () => props[prop] as string, set: (value) => (props[prop] = value) })\n }\n const values = props['propValues']\n if (values && typeof values === 'object' && !Array.isArray(values)) {\n const record = values as Record<string, unknown>\n for (const name of Object.keys(record)) {\n if (typeof record[name] === 'string') slots.push({ get: () => record[name] as string, set: (value) => (record[name] = value) })\n }\n }\n return slots\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction refsIn(text: string, names: AiLayoutRefNames): string[] {\n const found = (text.match(REF) ?? []).map((ref) => ref.trim())\n for (const id of names.ids) {\n if (id.length >= 6 && new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(id)}($|[^A-Za-z0-9_-])`).test(text)) found.push(id)\n }\n return [...new Set(found)]\n}\n\n/** An entry's own token, which a collection repeater fills per entry when the page renders. */\nconst ENTRY_TOKEN = /\\{\\{\\s*entry\\.[A-Za-z]+\\s*\\}\\}/g\n\n/** Every node under a Collection Entries block: its template, repeated once per entry (AGL-3676). */\nfunction repeatedByEntries(nodes: GapNodes): Set<string> {\n const repeated = new Set<string>()\n for (const [id, node] of Object.entries(nodes)) {\n if (node.componentId !== 'collectionEntries') continue\n for (const each of subtree(nodes, id)) if (each !== id) repeated.add(each)\n }\n return repeated\n}\n\nfunction refsOf(nodes: GapNodes, names: AiLayoutRefNames): Array<{ id: string; refs: string[]; text: string }> {\n // A post's card names its post by the entry's tokens, which the page fills\n // per post: words a visitor reads as the post's own, never a reference.\n const repeated = repeatedByEntries(nodes)\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = shownTexts(node).map((slot) => (repeated.has(id) ? slot.get().replace(ENTRY_TOKEN, '') : slot.get()))\n const refs = [...new Set(texts.flatMap((text) => refsIn(text, names)))]\n return refs.length ? [{ id, refs, text: texts.filter((text) => refsIn(text, names).length).join(' ') }] : []\n })\n}\n\n/** Internal references in a language-built page's or frame's words, as the violation that asks again. */\nexport function aiLayoutRefViolations(nodes: NodesMap, names: AiLayoutRefNames): AiDoctrineViolation[] {\n const found = refsOf(nodes as unknown as GapNodes, names)\n if (!found.length) return []\n const refs = [...new Set(found.flatMap((entry) => entry.refs))].map((ref) => `\"${ref}\"`)\n return [\n {\n rule: 14,\n code: AI_LAYOUT_REF_CODE,\n message: `${refs.join(', ')} ${refs.length === 1 ? 'is a link reference' : 'are link references'} written into words a visitor reads. A reference goes only in a button's or a list item's to; in text, name the page by its title, or leave the reference out.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\n/**\n * The page or frame with no internal reference in its words: a page's\n * reference is written as that page's title, and a line that still names a\n * reference is taken out, as a gap is. Returns what was rewritten and taken.\n */\nexport function aiLayoutWithoutRefs(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n): { nodes: NodesMap; dropped: string[] } {\n if (!refsOf(input as unknown as GapNodes, names).length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const labels = new Map(names.pages.map((page) => [page.id, page.label]))\n const dropped: string[] = []\n for (const node of Object.values(nodes)) {\n for (const slot of shownTexts(node)) {\n const before = slot.get()\n const after = before.replace(PAGE_REF, (ref, id: string) => labels.get(id) ?? ref)\n if (after !== before) {\n slot.set(after)\n dropped.push(`\"${before}\" written as \"${after}\"`)\n }\n }\n }\n dropped.push(...pruneFound(nodes, rootId, refsOf(nodes, names)))\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n/**\n * A language-built page's or frame's words held to both (AGL-3660): before\n * the last answer, each gap and each internal reference is a violation that\n * asks again; in the last, they are taken out and the rest is kept.\n */\nexport function aiLayoutCopyCheck(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n last: boolean,\n): { nodes: NodesMap; dropped: string[]; violations: AiDoctrineViolation[] } {\n if (!last) {\n return { nodes: input, dropped: [], violations: [...aiLayoutRefViolations(input, names), ...aiLayoutGapViolations(input)] }\n }\n const refs = aiLayoutWithoutRefs(input, rootId, names)\n const gaps = aiLayoutWithoutGaps(refs.nodes, rootId)\n return { nodes: gaps.nodes, dropped: [...refs.dropped, ...gaps.dropped], violations: [] }\n}\n"],"names":["aiBracketedFacts","aiStoredScrollTargets","NOT_WORDS","Set","WORD_PROPS","SPEAKS_ALONE","HEADING_VARIANTS","AI_LAYOUT_GAP_CODE","stringsOf","value","key","has","Array","isArray","flatMap","entry","Object","entries","name","gapsOf","nodes","id","node","texts","props","facts","length","text","filter","join","aiLayoutGapViolations","found","rule","code","message","nodeIds","map","parentsOf","parents","Map","child","set","subtree","ids","stack","next","pop","includes","push","wordsOf","prop","trim","isHeading","componentId","String","test","speaks","beyondHeadings","some","each","says","copyProps","values","propValues","cloneNodes","input","fromEntries","pruneFound","rootId","dropped","sections","touched","remove","parent","get","prune","section","add","above","first","label","aiLayoutWithoutGaps","AI_LAYOUT_REF_CODE","REF","PAGE_REF","SHOWN_PROPS","shownTexts","slots","record","keys","escapeRegExp","replace","refsIn","names","match","ref","RegExp","ENTRY_TOKEN","repeatedByEntries","repeated","refsOf","slot","refs","aiLayoutRefViolations","aiLayoutWithoutRefs","labels","pages","page","before","after","aiLayoutCopyCheck","last","violations","gaps"],"mappings":"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"}
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import type { AiLayoutListing } from './ai-layout-listings';
17
18
  /**
18
19
  * Where a link the layout language names goes (AGL-3660), resolved by code to
19
20
  * a destination rule 10 admits: a page of this site by its id, a section of
@@ -33,6 +34,12 @@ export interface AiLayoutPage {
33
34
  * no screen. Absent for every page.
34
35
  */
35
36
  href?: string;
37
+ /**
38
+ * The title of a planned page merged into this destination (AGL-3676): a
39
+ * page that stood in for the blog, kept only so links to it go to the blog.
40
+ * A link to it is named for the blog, not for the page it replaced.
41
+ */
42
+ standsInFor?: string;
36
43
  }
37
44
  /** Everything a page's links resolve against. */
38
45
  export interface AiLayoutTargets {
@@ -57,6 +64,12 @@ export interface AiLayoutTargets {
57
64
  }[];
58
65
  /** Every word the job was given: the brief, the site's answers, its profile. */
59
66
  facts: string;
67
+ /**
68
+ * The kinds of record the site keeps that its pages show — its catalog,
69
+ * its blog — and the sections that place each (AGL-3676,
70
+ * `ai-layout-listings.ts`). Absent or empty, a page shows only its words.
71
+ */
72
+ listings?: readonly AiLayoutListing[];
60
73
  }
61
74
  /** Where a link resolved to. */
62
75
  export type AiLayoutDestination = {
@@ -69,11 +82,23 @@ export type AiLayoutDestination = {
69
82
  kind: 'href';
70
83
  href: string;
71
84
  }
72
- /** A path on this site that is no page of its own, such as the blog (AGL-3660). */
85
+ /**
86
+ * A path on this site that is no page of its own, such as the blog
87
+ * (AGL-3660); reached through a planned page merged into it, the title
88
+ * that page had and the name it now goes by (AGL-3676).
89
+ */
73
90
  | {
74
91
  kind: 'path';
75
92
  href: string;
93
+ standsInFor?: string;
94
+ label?: string;
76
95
  };
96
+ /**
97
+ * A link's words for where it now goes (AGL-3676): a button that named a
98
+ * page merged into the blog — "Read the articles" — names the blog instead,
99
+ * "Read the blog". Words that name neither are kept.
100
+ */
101
+ export declare function aiLayoutRenamedLabel(label: string, destination: AiLayoutDestination): string;
77
102
  export interface AiLayoutLinkScope {
78
103
  /** The page's section names in plan order. */
79
104
  sections: readonly string[];