@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.288

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 (228) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  3. package/src/lib/assets/site-styles/professional.webp +0 -0
  4. package/src/lib/assets/site-styles/store.webp +0 -0
  5. package/src/lib/assets/site-styles/trades.webp +0 -0
  6. package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
  7. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  8. package/src/lib/declarations.js +5 -2
  9. package/src/lib/declarations.js.map +1 -1
  10. package/src/lib/declarations.server.js +12 -0
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
  13. package/src/lib/jobs/ai-build-capabilities.js +27 -2
  14. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  15. package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
  16. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  17. package/src/lib/jobs/ai-image-reads.d.ts +59 -0
  18. package/src/lib/jobs/ai-image-reads.js +138 -0
  19. package/src/lib/jobs/ai-image-reads.js.map +1 -0
  20. package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
  21. package/src/lib/jobs/ai-job-build-step.js +83 -12
  22. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  23. package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
  24. package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
  25. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
  27. package/src/lib/jobs/ai-job-import-step.js +15 -11
  28. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
  30. package/src/lib/jobs/ai-job-insight-step.js +23 -5
  31. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  32. package/src/lib/jobs/ai-job-layout-language.js +2 -1
  33. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  34. package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
  35. package/src/lib/jobs/ai-job-media-step.js +6 -8
  36. package/src/lib/jobs/ai-job-media-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
  38. package/src/lib/jobs/ai-job-navigation-step.js +7 -9
  39. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-page-language.js +7 -1
  41. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-sections.js +16 -3
  43. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  44. package/src/lib/jobs/ai-job-page-step.js +9 -1
  45. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  46. package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
  47. package/src/lib/jobs/ai-job-plan-step.js +78 -13
  48. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
  50. package/src/lib/jobs/ai-job-privacy-step.js +14 -15
  51. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-products-step.js +5 -3
  53. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
  55. package/src/lib/jobs/ai-job-settings-step.js +7 -8
  56. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
  58. package/src/lib/jobs/ai-job-site-content.js +67 -8
  59. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  60. package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
  61. package/src/lib/jobs/ai-job-site-datasets.js +300 -30
  62. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-step.js +6 -3
  64. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
  66. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
  67. package/src/lib/jobs/ai-job-text-step.js +2 -1
  68. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  69. package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
  70. package/src/lib/jobs/ai-job-tracking-step.js +7 -8
  71. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  72. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
  73. package/src/lib/jobs/ai-jobs-beat.js +18 -8
  74. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  75. package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
  76. package/src/lib/jobs/ai-jobs-notify.js +136 -3
  77. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  78. package/src/lib/jobs/ai-layout-stock-photos.d.ts +155 -7
  79. package/src/lib/jobs/ai-layout-stock-photos.js +1517 -220
  80. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  81. package/src/lib/jobs/ai-product-image.d.ts +14 -0
  82. package/src/lib/jobs/ai-product-image.js +73 -21
  83. package/src/lib/jobs/ai-product-image.js.map +1 -1
  84. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
  85. package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
  86. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  87. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
  88. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
  89. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
  90. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
  91. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
  92. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
  93. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
  94. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
  95. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
  96. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
  97. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
  98. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
  99. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
  100. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
  101. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
  102. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
  103. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
  104. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
  105. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
  106. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
  107. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
  108. package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
  109. package/src/lib/layout-language/ai-layout-compiler.js +194 -18
  110. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  111. package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
  112. package/src/lib/layout-language/ai-layout-copy.js +383 -3
  113. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  114. package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
  115. package/src/lib/layout-language/ai-layout-design.js +31 -11
  116. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  117. package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
  118. package/src/lib/layout-language/ai-layout-listings.js +9 -1
  119. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  120. package/src/lib/layout-language/ai-layout-pictures.d.ts +39 -1
  121. package/src/lib/layout-language/ai-layout-pictures.js +141 -4
  122. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  123. package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
  124. package/src/lib/layout-language/ai-layout-storefront.js +100 -20
  125. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
  126. package/src/lib/model/ai-build-job.d.ts +7 -0
  127. package/src/lib/model/ai-build-job.js +36 -7
  128. package/src/lib/model/ai-build-job.js.map +1 -1
  129. package/src/lib/model/ai-build-plan.d.ts +28 -4
  130. package/src/lib/model/ai-build-plan.js +49 -9
  131. package/src/lib/model/ai-build-plan.js.map +1 -1
  132. package/src/lib/model/ai-build-progress.js +3 -1
  133. package/src/lib/model/ai-build-progress.js.map +1 -1
  134. package/src/lib/model/ai-job-notice.d.ts +8 -0
  135. package/src/lib/model/ai-job-notice.js +33 -0
  136. package/src/lib/model/ai-job-notice.js.map +1 -1
  137. package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
  138. package/src/lib/model/ai-job-plan-lines.js +6 -1
  139. package/src/lib/model/ai-job-plan-lines.js.map +1 -1
  140. package/src/lib/model/ai-jobs.types.d.ts +8 -0
  141. package/src/lib/model/ai-jobs.types.js.map +1 -1
  142. package/src/lib/model/ai-permissions.d.ts +13 -0
  143. package/src/lib/model/ai-permissions.js +29 -1
  144. package/src/lib/model/ai-permissions.js.map +1 -1
  145. package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
  146. package/src/lib/model/ai-plan-said-sections.js +22 -5
  147. package/src/lib/model/ai-plan-said-sections.js.map +1 -1
  148. package/src/lib/model/ai-products.d.ts +18 -0
  149. package/src/lib/model/ai-products.js +32 -0
  150. package/src/lib/model/ai-products.js.map +1 -1
  151. package/src/lib/model/ai-site-context.d.ts +6 -2
  152. package/src/lib/model/ai-site-context.js +8 -2
  153. package/src/lib/model/ai-site-context.js.map +1 -1
  154. package/src/lib/model/ai-site-inspiration.d.ts +3 -2
  155. package/src/lib/model/ai-site-inspiration.js +3 -2
  156. package/src/lib/model/ai-site-inspiration.js.map +1 -1
  157. package/src/lib/plugin.js +4 -13
  158. package/src/lib/plugin.js.map +1 -1
  159. package/src/lib/providers/anthropic.js +9 -2
  160. package/src/lib/providers/anthropic.js.map +1 -1
  161. package/src/lib/providers/catalog.js +14 -5
  162. package/src/lib/providers/catalog.js.map +1 -1
  163. package/src/lib/providers/contract.d.ts +24 -1
  164. package/src/lib/providers/contract.js.map +1 -1
  165. package/src/lib/providers/routing.js +5 -2
  166. package/src/lib/providers/routing.js.map +1 -1
  167. package/src/lib/runtime/ai-dataset-generation.d.ts +120 -1
  168. package/src/lib/runtime/ai-dataset-generation.js +352 -3
  169. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  170. package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
  171. package/src/lib/runtime/ai-dev-replay.js +4 -2
  172. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  173. package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
  174. package/src/lib/runtime/ai-doctrine-validators.js +345 -28
  175. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  176. package/src/lib/runtime/ai-doctrine.d.ts +5 -4
  177. package/src/lib/runtime/ai-doctrine.js +10 -3
  178. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  179. package/src/lib/runtime/ai-icon-library.d.ts +27 -1
  180. package/src/lib/runtime/ai-icon-library.js +30 -3
  181. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  182. package/src/lib/runtime/ai-products-generation.d.ts +11 -1
  183. package/src/lib/runtime/ai-products-generation.js +51 -4
  184. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  185. package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
  186. package/src/lib/runtime/assist-answer-cache.js +5 -2
  187. package/src/lib/runtime/assist-answer-cache.js.map +1 -1
  188. package/src/lib/server/ai-allotments.js +7 -2
  189. package/src/lib/server/ai-allotments.js.map +1 -1
  190. package/src/lib/server/ai-experiment-versions.js +5 -5
  191. package/src/lib/server/ai-experiment-versions.js.map +1 -1
  192. package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
  193. package/src/lib/server/ai-host-permissions-roster.js +33 -0
  194. package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
  195. package/src/lib/server/ai-host-permissions.js +8 -6
  196. package/src/lib/server/ai-host-permissions.js.map +1 -1
  197. package/src/lib/server/ai-image-reads-route.d.ts +17 -0
  198. package/src/lib/server/ai-image-reads-route.js +106 -0
  199. package/src/lib/server/ai-image-reads-route.js.map +1 -0
  200. package/src/lib/server/ai-seo-apply.js +5 -5
  201. package/src/lib/server/ai-seo-apply.js.map +1 -1
  202. package/src/lib/server/ai-site-import-route.js +7 -4
  203. package/src/lib/server/ai-site-import-route.js.map +1 -1
  204. package/src/lib/server/assist-build.d.ts +15 -1
  205. package/src/lib/server/assist-build.js +30 -3
  206. package/src/lib/server/assist-build.js.map +1 -1
  207. package/src/lib/server/assist-chat.d.ts +11 -7
  208. package/src/lib/server/assist-chat.js +185 -48
  209. package/src/lib/server/assist-chat.js.map +1 -1
  210. package/src/lib/server/assist-docs-query.d.ts +51 -0
  211. package/src/lib/server/assist-docs-query.js +57 -0
  212. package/src/lib/server/assist-docs-query.js.map +1 -0
  213. package/src/lib/server/assist-jobs-context.d.ts +102 -0
  214. package/src/lib/server/assist-jobs-context.js +227 -0
  215. package/src/lib/server/assist-jobs-context.js.map +1 -0
  216. package/src/lib/server/assist-view-context.d.ts +8 -4
  217. package/src/lib/server/assist-view-context.js +12 -7
  218. package/src/lib/server/assist-view-context.js.map +1 -1
  219. package/src/lib/server.js +12 -0
  220. package/src/lib/server.js.map +1 -1
  221. package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
  222. package/src/lib/tools/ai-dataset-tool.js +24 -7
  223. package/src/lib/tools/ai-dataset-tool.js.map +1 -1
  224. package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
  225. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  226. package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
  227. package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
  228. package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
@@ -30,7 +30,7 @@ import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome, aiSi
30
30
  import { aiLayoutIsShopPage, aiLayoutShopGridSection } from "../layout-language/ai-layout-listings.js";
31
31
  import { aiStorefrontSays } from "../layout-language/ai-layout-storefront.js";
32
32
  import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
33
- import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets } from "./ai-page-links.js";
33
+ import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets, isAiFragmentHref } from "./ai-page-links.js";
34
34
  import { AI_EMAIL_CLIP_BYTES, AI_OUTPUT_BUDGETS, AI_OUTPUT_SURFACE, AI_TEXT_LIMITS, isHeadlineVariant } from "./ai-palette.js";
35
35
  import { AI_PALETTE, AI_SX_TOKENS } from "./ai-palette.generated.js";
36
36
  import { aiIsOnlyPlaceholders, aiPlaceholderTokens } from "./ai-placeholder-tokens.js";
@@ -2151,6 +2151,162 @@ const GRID = 'muiGrid';
2151
2151
  }
2152
2152
  return violations;
2153
2153
  }
2154
+ /** The longest send label a Form takes, as its palette entry bounds `submitLabel`. */ const FORM_SUBMIT_LABEL_MAX = 40;
2155
+ /** The frames a link may be the last thing in, taken out with it rather than left empty. */ const LINK_FRAMES = new Set([
2156
+ 'muiBox',
2157
+ 'muiStack',
2158
+ 'muiContainer',
2159
+ 'muiGrid',
2160
+ 'section',
2161
+ 'div',
2162
+ 'muiCardActions'
2163
+ ]);
2164
+ /**
2165
+ * The links `detectLinksWithoutDestination` would refuse as going nowhere
2166
+ * (`link-without-destination`) or to an anchor no section has
2167
+ * (`link-fragment`), read conservatively: a link whose `screenId` names a page
2168
+ * the site has, whose `href` is anything but a fragment, that carries a Scroll
2169
+ * to element interaction to a section of its page, or whose `scrollTo` the
2170
+ * check reads on its own terms (`scroll-target-unknown`), is not one. A tree
2171
+ * as written is read as the check will read it once the palette validator has
2172
+ * dropped the fragment.
2173
+ */ function linksWithoutDestination(tree, outputKind, context) {
2174
+ var _context_pageSections;
2175
+ if (outputKind === 'email' || outputKind === 'form') return [];
2176
+ const sections = (_context_pageSections = context.pageSections) != null ? _context_pageSections : [];
2177
+ const targets = context.scrollTargetIds ? new Set(context.scrollTargetIds) : null;
2178
+ const known = context.screenIds ? new Set(context.screenIds) : null;
2179
+ const written = (id)=>context.writtenNode ? context.writtenNode(id) : tree.nodes[id];
2180
+ const found = [];
2181
+ for (const visit of walkTree(tree)){
2182
+ var _reverse_find;
2183
+ var _node_props, _node_props1, _node_props2;
2184
+ const { node } = visit;
2185
+ if (!LINK_COMPONENTS.has(node.componentId)) continue;
2186
+ const screenId = (_node_props = node.props) == null ? void 0 : _node_props['screenId'];
2187
+ if (isFilled(screenId) && (!known || known.has(String(screenId).trim()))) continue;
2188
+ const href = (_node_props1 = node.props) == null ? void 0 : _node_props1['href'];
2189
+ if (isFilled(href) && !isAiFragmentHref(href)) continue;
2190
+ const scrolls = aiStoredScrollTargets(node);
2191
+ if (scrolls.length && (!targets || scrolls.some((target)=>targets.has(target)))) continue;
2192
+ const target = aiPageLinkTarget(written(visit.id), sections);
2193
+ if ((target == null ? void 0 : target.kind) === 'section' || (target == null ? void 0 : target.kind) === 'unknown' && target.via === 'scrollTo') continue;
2194
+ const label = typeof ((_node_props2 = node.props) == null ? void 0 : _node_props2['children']) === 'string' ? node.props['children'].trim() : '';
2195
+ const form = (_reverse_find = [
2196
+ ...visit.ancestors
2197
+ ].reverse().find((id)=>{
2198
+ var _tree_nodes_id;
2199
+ return ((_tree_nodes_id = tree.nodes[id]) == null ? void 0 : _tree_nodes_id.componentId) === 'form';
2200
+ })) != null ? _reverse_find : null;
2201
+ found.push({
2202
+ id: visit.id,
2203
+ label,
2204
+ form
2205
+ });
2206
+ }
2207
+ return found;
2208
+ }
2209
+ /**
2210
+ * RULE 10'S LINK THAT GOES NOWHERE, SETTLED IN CODE (AGL-4076).
2211
+ *
2212
+ * A Button or a Page Link with no destination (`link-without-destination`),
2213
+ * or one sent to an anchor no section of its page has (`link-fragment`),
2214
+ * stopped 5 of the 12 findings behind the 10/10 to 10/28 doctrine stops: the
2215
+ * re-ask is spent, the job stops, and Try again asks the same. Each has one
2216
+ * answer the rule itself gives, so it is applied before the check reads the
2217
+ * tree, as `aiSettleUnrelatedScreenLinks` settles a link sent home:
2218
+ *
2219
+ * - a button drawn inside a Form is taken out, since the Form draws its own
2220
+ * send button; its words become the Form's `submitLabel` when the Form has
2221
+ * none of its own;
2222
+ * - a link whose words ask a visitor to get in touch is sent to the page that
2223
+ * places the site's form, when the site has one that is not this page;
2224
+ * - any other is left out: no page can be chosen for it from its words alone.
2225
+ *
2226
+ * A frame the link was the last thing in is taken out with it rather than
2227
+ * left empty, up to the page's own sections, which always stay. A link to a
2228
+ * section of its page, to a page the site has, or to any address is never
2229
+ * touched, and nor is one whose `scrollTo` names no section, which the check
2230
+ * re-asks as before. The root is never taken out. A tree with nothing to
2231
+ * settle comes back as the same object; a node that is changed is copied.
2232
+ */ export function aiSettleLinksWithoutDestination(tree, outputKind, context = {}) {
2233
+ var _context_ownScreenIds, _find, _context_formScreenIds, _tree_nodes_tree_rootId_nodes, _context_scrollTargetIds;
2234
+ if (!tree.nodes[tree.rootId]) return tree;
2235
+ const found = linksWithoutDestination(tree, outputKind, context);
2236
+ if (!found.length) return tree;
2237
+ const own = new Set((_context_ownScreenIds = context.ownScreenIds) != null ? _context_ownScreenIds : []);
2238
+ const formScreen = (_find = ((_context_formScreenIds = context.formScreenIds) != null ? _context_formScreenIds : []).find((id)=>!own.has(id))) != null ? _find : null;
2239
+ // A page's sections, and anything a link scrolls to, always stay.
2240
+ const kept = new Set([
2241
+ tree.rootId,
2242
+ ...(_tree_nodes_tree_rootId_nodes = tree.nodes[tree.rootId].nodes) != null ? _tree_nodes_tree_rootId_nodes : [],
2243
+ ...(_context_scrollTargetIds = context.scrollTargetIds) != null ? _context_scrollTargetIds : []
2244
+ ]);
2245
+ const nodes = _extends({}, tree.nodes);
2246
+ const parents = new Map();
2247
+ for (const visit of walkTree(tree)){
2248
+ const parent = visit.ancestors[visit.ancestors.length - 1];
2249
+ if (parent !== undefined) parents.set(visit.id, parent);
2250
+ }
2251
+ let changed = false;
2252
+ const takeOut = (id)=>{
2253
+ let next = id;
2254
+ while(next !== undefined && nodes[next] && !kept.has(next)){
2255
+ var _nodes_parent_nodes;
2256
+ const parent = parents.get(next);
2257
+ if (parent === undefined || !nodes[parent]) return;
2258
+ const children = ((_nodes_parent_nodes = nodes[parent].nodes) != null ? _nodes_parent_nodes : []).filter((child)=>child !== next);
2259
+ nodes[parent] = _extends({}, nodes[parent], {
2260
+ nodes: children
2261
+ });
2262
+ changed = true;
2263
+ const stack = [
2264
+ next
2265
+ ];
2266
+ while(stack.length){
2267
+ var _ref;
2268
+ var _nodes_gone;
2269
+ const gone = stack.pop();
2270
+ stack.push(...(_ref = (_nodes_gone = nodes[gone]) == null ? void 0 : _nodes_gone.nodes) != null ? _ref : []);
2271
+ delete nodes[gone];
2272
+ }
2273
+ next = !children.some((child)=>nodes[child]) && LINK_FRAMES.has(nodes[parent].componentId) ? parent : undefined;
2274
+ }
2275
+ };
2276
+ for (const { id, label, form } of found){
2277
+ if (!nodes[id]) continue;
2278
+ if (form && nodes[form]) {
2279
+ var _nodes_form_props;
2280
+ if (label && label.length <= FORM_SUBMIT_LABEL_MAX && !isFilled((_nodes_form_props = nodes[form].props) == null ? void 0 : _nodes_form_props['submitLabel'])) {
2281
+ var _nodes_form_props1;
2282
+ nodes[form] = _extends({}, nodes[form], {
2283
+ props: _extends({}, (_nodes_form_props1 = nodes[form].props) != null ? _nodes_form_props1 : {}, {
2284
+ submitLabel: label
2285
+ })
2286
+ });
2287
+ changed = true;
2288
+ }
2289
+ takeOut(id);
2290
+ continue;
2291
+ }
2292
+ if (formScreen && label && aiAsksToGetInTouch(label)) {
2293
+ var _nodes_id_props;
2294
+ const props = _extends({}, (_nodes_id_props = nodes[id].props) != null ? _nodes_id_props : {}, {
2295
+ screenId: formScreen
2296
+ });
2297
+ delete props['href'];
2298
+ nodes[id] = _extends({}, nodes[id], {
2299
+ props
2300
+ });
2301
+ changed = true;
2302
+ continue;
2303
+ }
2304
+ takeOut(id);
2305
+ }
2306
+ return changed ? _extends({}, tree, {
2307
+ nodes
2308
+ }) : tree;
2309
+ }
2154
2310
  /** Answer fields that say "publish", wherever the answer put them. */ const PUBLISH_KEYS = /^(?:publish|published|publishNow|publishAt|goLive|isLive|live|makeLive)$/i;
2155
2311
  /**
2156
2312
  * Rule 13. The AI writes drafts. An answer that asks for anything to be
@@ -2613,6 +2769,29 @@ const EMBED_COMPONENTS = new Set([
2613
2769
  ]);
2614
2770
  const DUPLICATE_SX_MIN_KEYS = 2;
2615
2771
  const DUPLICATE_SX_MIN_NODES = 4;
2772
+ /**
2773
+ * Rule 16's repeated style: the elements whose `sx` is one style of at least
2774
+ * `DUPLICATE_SX_MIN_KEYS` keys, grouped by that style, each group of at least
2775
+ * `DUPLICATE_SX_MIN_NODES` elements, in document order.
2776
+ */ function duplicatedSxGroups(tree) {
2777
+ const seen = new Map();
2778
+ for (const { id, node } of walkTree(tree)){
2779
+ var _node_sx, _seen_get;
2780
+ const sx = (_node_sx = node.sx) != null ? _node_sx : {};
2781
+ if (Object.keys(sx).length < DUPLICATE_SX_MIN_KEYS) continue;
2782
+ const key = JSON.stringify(Object.keys(sx).sort().map((name)=>[
2783
+ name,
2784
+ sx[name]
2785
+ ]));
2786
+ seen.set(key, [
2787
+ ...(_seen_get = seen.get(key)) != null ? _seen_get : [],
2788
+ id
2789
+ ]);
2790
+ }
2791
+ return [
2792
+ ...seen.values()
2793
+ ].filter((ids)=>ids.length >= DUPLICATE_SX_MIN_NODES);
2794
+ }
2616
2795
  /**
2617
2796
  * Whether a third-party player is one the confirmed plan lists (AGL-3433). A
2618
2797
  * Video embed is admitted where the plan lists a player, playing the video
@@ -2648,11 +2827,10 @@ const DUPLICATE_SX_MIN_NODES = 4;
2648
2827
  const video = [];
2649
2828
  const embeds = [];
2650
2829
  const embedLinks = [];
2651
- const sxSeen = new Map();
2652
2830
  let images = 0;
2653
2831
  for (const { id, node } of visits){
2654
- var _node_nodes, _node_props, _node_sx, _node_sx1;
2655
- var _node_props1, _node_sx2, _node_props2, _node_props3, _node_props4, _node_props5;
2832
+ var _node_nodes, _node_props, _node_sx;
2833
+ var _node_props1, _node_sx1, _node_props2, _node_props3, _node_props4, _node_props5;
2656
2834
  const entry = AI_PALETTE[node.componentId];
2657
2835
  const children = ((_node_nodes = node.nodes) != null ? _node_nodes : []).filter((child)=>tree.nodes[child]);
2658
2836
  const ownProps = Object.entries((_node_props = node.props) != null ? _node_props : {}).filter(([name, value])=>!(name === 'component' && value === 'div'));
@@ -2663,7 +2841,7 @@ const DUPLICATE_SX_MIN_NODES = 4;
2663
2841
  if ((entry == null ? void 0 : entry.acceptsChildren) && !children.length && !boundForm && !OWNS_ITS_EMPTY_STATE.has(node.componentId)) {
2664
2842
  empty.push(id);
2665
2843
  }
2666
- if (outputKind !== 'email' && ((_node_sx2 = node.sx) == null ? void 0 : _node_sx2['fontFamily']) !== undefined) fonts.push(id);
2844
+ if (outputKind !== 'email' && ((_node_sx1 = node.sx) == null ? void 0 : _node_sx1['fontFamily']) !== undefined) fonts.push(id);
2667
2845
  if (node.componentId === 'image') {
2668
2846
  var _node_props6;
2669
2847
  images += 1;
@@ -2678,22 +2856,8 @@ const DUPLICATE_SX_MIN_NODES = 4;
2678
2856
  if (verdict === 'unplanned') embeds.push(id);
2679
2857
  if (verdict === 'unplanned-link') embedLinks.push(id);
2680
2858
  }
2681
- const sx = (_node_sx1 = node.sx) != null ? _node_sx1 : {};
2682
- if (Object.keys(sx).length >= DUPLICATE_SX_MIN_KEYS) {
2683
- var _sxSeen_get;
2684
- const key = JSON.stringify(Object.keys(sx).sort().map((name)=>[
2685
- name,
2686
- sx[name]
2687
- ]));
2688
- sxSeen.set(key, [
2689
- ...(_sxSeen_get = sxSeen.get(key)) != null ? _sxSeen_get : [],
2690
- id
2691
- ]);
2692
- }
2693
2859
  }
2694
- const duplicatedSx = [
2695
- ...sxSeen.values()
2696
- ].filter((ids)=>ids.length >= DUPLICATE_SX_MIN_NODES).flat();
2860
+ const duplicatedSx = duplicatedSxGroups(tree).flat();
2697
2861
  const violations = [];
2698
2862
  const add = (code, ids, message)=>{
2699
2863
  if (ids.length) violations.push({
@@ -3416,6 +3580,52 @@ const BUDGET_METRICS = [
3416
3580
  }) : input;
3417
3581
  }
3418
3582
  // ── Mechanical rules, settled in code (AGL-3660) ─────────────────────────
3583
+ /** The inherited styles a repeated style may be carried once in, by the frame that holds every element repeating it (AGL-4076). */ const HOISTED_SX_KEYS = [
3584
+ 'textAlign',
3585
+ 'color'
3586
+ ];
3587
+ /**
3588
+ * The elements that take each of those styles from their parent when they set
3589
+ * none themselves, which are also the frames that may carry one for the
3590
+ * elements they hold: none of them draws anything of its own in it.
3591
+ */ const INHERITS_SX = {
3592
+ // A Typography aligns as `inherit` unless its `align` says otherwise; a Card and a Paper set no alignment.
3593
+ textAlign: new Set([
3594
+ 'muiTypography',
3595
+ 'muiBox',
3596
+ 'muiStack',
3597
+ 'muiGrid',
3598
+ 'muiContainer',
3599
+ 'section',
3600
+ 'div',
3601
+ 'muiCardContent',
3602
+ 'muiCard',
3603
+ 'muiPaper'
3604
+ ]),
3605
+ // A Card and a Paper paint their own text color, and a Button, a link and a list row theirs.
3606
+ color: new Set([
3607
+ 'muiTypography',
3608
+ 'muiBox',
3609
+ 'muiStack',
3610
+ 'muiGrid',
3611
+ 'muiContainer',
3612
+ 'section',
3613
+ 'div',
3614
+ 'muiCardContent'
3615
+ ])
3616
+ };
3617
+ /** A Typography's `align` values, which draw exactly what the same `sx` text alignment does. */ const TYPOGRAPHY_ALIGNS = new Set([
3618
+ 'inherit',
3619
+ 'left',
3620
+ 'center',
3621
+ 'right',
3622
+ 'justify'
3623
+ ]);
3624
+ /** An `sx` without one key. */ function sxWithout(sx, key) {
3625
+ const rest = _extends({}, sx != null ? sx : {});
3626
+ delete rest[key];
3627
+ return rest;
3628
+ }
3419
3629
  /**
3420
3630
  * THE RULES A CHECK WOULD REFUSE FOR AN ATTRIBUTE, SETTLED IN CODE (AGL-3660).
3421
3631
  *
@@ -3451,11 +3661,20 @@ const BUDGET_METRICS = [
3451
3661
  * - `page-text-on-band` (5): words on a band in a family's main or dark shade
3452
3662
  * whose `sx` color is a page text color (`text.secondary`) are given the
3453
3663
  * family's contrast text, and a List Item Text's secondary words there take
3454
- * the row's own color (`secondaryColor: inherit`).
3664
+ * the row's own color (`secondaryColor: inherit`);
3665
+ * - `duplicated-inline-style` (16), where the style can be carried once
3666
+ * without changing what is drawn (AGL-4076): when the elements repeating it
3667
+ * are every element of one frame, its text alignment and its text color
3668
+ * move to that frame, which they inherit from, and a Typography's text
3669
+ * alignment becomes its `align`. Only where that leaves fewer repeats; a
3670
+ * repeated padding, size or border has no single place to go and is still
3671
+ * the re-ask's;
3672
+ * - `link-without-destination` and `link-fragment` (10), given `links`, the
3673
+ * context the check reads links in (`aiSettleLinksWithoutDestination`).
3455
3674
  *
3456
3675
  * A tree with nothing to settle comes back as the same object, so settling is
3457
3676
  * idempotent; a node that is changed is copied, never edited in place.
3458
- */ export function aiSettleMechanicalRules(tree, outputKind) {
3677
+ */ export function aiSettleMechanicalRules(tree, outputKind, links) {
3459
3678
  if (!tree.nodes[tree.rootId]) return tree;
3460
3679
  const nodes = _extends({}, tree.nodes);
3461
3680
  const copied = new Set();
@@ -3590,25 +3809,122 @@ const BUDGET_METRICS = [
3590
3809
  });
3591
3810
  }
3592
3811
  }
3593
- return copied.size ? _extends({}, tree, {
3812
+ // Rule 16: a style a group of elements repeats, carried once where it draws the same (AGL-4076).
3813
+ if (framed) {
3814
+ const parentOf = new Map();
3815
+ for (const visit of walkTree({
3816
+ rootId: tree.rootId,
3817
+ nodes
3818
+ })){
3819
+ const parent = visit.ancestors[visit.ancestors.length - 1];
3820
+ if (parent !== undefined) parentOf.set(visit.id, parent);
3821
+ }
3822
+ const heavy = ()=>detectHeavyDocument({
3823
+ rootId: tree.rootId,
3824
+ nodes
3825
+ }, outputKind).reduce((sum, violation)=>{
3826
+ var _ref;
3827
+ var _violation_nodeIds;
3828
+ return sum + ((_ref = (_violation_nodeIds = violation.nodeIds) == null ? void 0 : _violation_nodeIds.length) != null ? _ref : 0);
3829
+ }, 0);
3830
+ for (const group of duplicatedSxGroups({
3831
+ rootId: tree.rootId,
3832
+ nodes
3833
+ })){
3834
+ var _ref, _ref1;
3835
+ var _nodes_group_, _nodes_parent;
3836
+ const style = (_ref = (_nodes_group_ = nodes[group[0]]) == null ? void 0 : _nodes_group_.sx) != null ? _ref : {};
3837
+ const same = JSON.stringify(Object.keys(style).sort().map((name)=>[
3838
+ name,
3839
+ style[name]
3840
+ ]));
3841
+ const alike = group.every((id)=>{
3842
+ var _ref;
3843
+ var _nodes_id;
3844
+ const sx = (_ref = (_nodes_id = nodes[id]) == null ? void 0 : _nodes_id.sx) != null ? _ref : {};
3845
+ return JSON.stringify(Object.keys(sx).sort().map((name)=>[
3846
+ name,
3847
+ sx[name]
3848
+ ])) === same;
3849
+ });
3850
+ if (!alike) continue;
3851
+ const before = heavy();
3852
+ const replaced = new Map();
3853
+ const wasCopied = new Set(copied);
3854
+ const put = (id, node)=>{
3855
+ if (!replaced.has(id)) replaced.set(id, nodes[id]);
3856
+ nodes[id] = node;
3857
+ copied.add(id);
3858
+ };
3859
+ const parent = parentOf.get(group[0]);
3860
+ const held = parent !== undefined ? ((_ref1 = (_nodes_parent = nodes[parent]) == null ? void 0 : _nodes_parent.nodes) != null ? _ref1 : []).filter((child)=>nodes[child]) : [];
3861
+ const whole = parent !== undefined && parent !== tree.rootId && held.length === group.length && group.every((id)=>parentOf.get(id) === parent);
3862
+ for (const key of whole ? HOISTED_SX_KEYS : []){
3863
+ var _holder_sx, _holder_sx1;
3864
+ var _holder_sx2;
3865
+ const holder = nodes[parent];
3866
+ if (style[key] === undefined || !INHERITS_SX[key].has(holder.componentId)) continue;
3867
+ if (!group.every((id)=>INHERITS_SX[key].has(nodes[id].componentId))) continue;
3868
+ const own = (_holder_sx2 = holder.sx) == null ? void 0 : _holder_sx2[key];
3869
+ if (own !== undefined && JSON.stringify(own) !== JSON.stringify(style[key])) continue;
3870
+ // A border drawn in the frame's own text color would take the new one.
3871
+ if (key === 'color' && Object.keys((_holder_sx = holder.sx) != null ? _holder_sx : {}).some((name)=>/^(?:border|outline)/.test(name))) continue;
3872
+ put(parent, _extends({}, holder, {
3873
+ sx: _extends({}, (_holder_sx1 = holder.sx) != null ? _holder_sx1 : {}, {
3874
+ [key]: style[key]
3875
+ })
3876
+ }));
3877
+ for (const id of group)put(id, _extends({}, nodes[id], {
3878
+ sx: sxWithout(nodes[id].sx, key)
3879
+ }));
3880
+ }
3881
+ for (const id of group){
3882
+ var _node_props7;
3883
+ var _node_sx4, _node_props8;
3884
+ const node = nodes[id];
3885
+ const align = (_node_sx4 = node.sx) == null ? void 0 : _node_sx4['textAlign'];
3886
+ if (node.componentId !== 'muiTypography' || typeof align !== 'string' || !TYPOGRAPHY_ALIGNS.has(align)) continue;
3887
+ if (((_node_props8 = node.props) == null ? void 0 : _node_props8['align']) !== undefined && node.props['align'] !== 'inherit') continue;
3888
+ put(id, _extends({}, node, {
3889
+ props: _extends({}, (_node_props7 = node.props) != null ? _node_props7 : {}, {
3890
+ align
3891
+ }),
3892
+ sx: sxWithout(node.sx, 'textAlign')
3893
+ }));
3894
+ }
3895
+ // Kept only where it leaves the page lighter by the rule's own measure.
3896
+ if (replaced.size && heavy() >= before) {
3897
+ for (const [id, node] of replaced)nodes[id] = node;
3898
+ copied.clear();
3899
+ for (const id of wasCopied)copied.add(id);
3900
+ }
3901
+ }
3902
+ }
3903
+ const settled = copied.size ? _extends({}, tree, {
3594
3904
  nodes
3595
3905
  }) : tree;
3906
+ return links ? aiSettleLinksWithoutDestination(settled, outputKind, links) : settled;
3596
3907
  }
3597
3908
  /**
3598
3909
  * `aiSettleMechanicalRules` on a tree as the model wrote it, before the
3599
3910
  * palette validator reads it (AGL-3660), as `aiSettleWrittenGridItems` settles
3600
3911
  * its items. Anything that is no flat node map comes back as it was, and a
3601
3912
  * node whose shape is not a palette node's is read as nothing and left as written.
3602
- */ export function aiSettleWrittenMechanicalRules(input, outputKind) {
3913
+ * Given `links`, a link that goes nowhere is settled too (AGL-4076), and a
3914
+ * node that takes out is gone from the answer.
3915
+ */ export function aiSettleWrittenMechanicalRules(input, outputKind, links) {
3603
3916
  if (!isRecord(input) || typeof input['rootId'] !== 'string' || !isRecord(input['nodes'])) return input;
3604
3917
  const readable = Object.entries(input['nodes']).filter(([, node])=>isRecord(node) && typeof node['componentId'] === 'string' && (node['props'] === undefined || isRecord(node['props'])) && (node['sx'] === undefined || isRecord(node['sx'])) && (node['nodes'] === undefined || Array.isArray(node['nodes']) && node['nodes'].every((child)=>typeof child === 'string')));
3605
3918
  const tree = {
3606
3919
  rootId: input['rootId'],
3607
3920
  nodes: Object.fromEntries(readable)
3608
3921
  };
3609
- const settled = aiSettleMechanicalRules(tree, outputKind);
3610
- return settled === tree ? input : _extends({}, input, {
3611
- nodes: _extends({}, input['nodes'], settled.nodes)
3922
+ const settled = aiSettleMechanicalRules(tree, outputKind, links);
3923
+ if (settled === tree) return input;
3924
+ const nodes = _extends({}, input['nodes'], settled.nodes);
3925
+ for (const id of Object.keys(tree.nodes))if (!settled.nodes[id]) delete nodes[id];
3926
+ return _extends({}, input, {
3927
+ nodes
3612
3928
  });
3613
3929
  }
3614
3930
  /**
@@ -5197,7 +5513,8 @@ function slugKey(slug) {
5197
5513
  */ export const AI_FREE_PAGE_WORST_CASE_CREDITS = {
5198
5514
  plan: 83,
5199
5515
  layout: 64,
5200
- firstSection: 44,
5516
+ // 44 until AGL-4092, whose rule 14 every section pass's cached prefix carries.
5517
+ firstSection: 45,
5201
5518
  laterSection: 20,
5202
5519
  // A page's listing, on the fast tier at the platform's markup since
5203
5520
  // AGL-3781 (3 credits at Claude Haiku 4.5's cost before).