@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.247

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 (173) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +7 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +21 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  13. package/src/lib/components/ai-site-start-card.component.js +98 -23
  14. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  16. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  17. package/src/lib/declarations.server.js +12 -0
  18. package/src/lib/declarations.server.js.map +1 -1
  19. package/src/lib/jobs/ai-build-capabilities.js +216 -2
  20. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.d.ts +3 -1
  22. package/src/lib/jobs/ai-job-build-step.js +124 -18
  23. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  25. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  26. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  27. package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
  28. package/src/lib/jobs/ai-job-form-dataset.js +28 -10
  29. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  30. package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
  31. package/src/lib/jobs/ai-job-form-step.js +98 -28
  32. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  33. package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
  34. package/src/lib/jobs/ai-job-layout-language.js +29 -5
  35. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  36. package/src/lib/jobs/ai-job-layout-step.js +4 -1
  37. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
  39. package/src/lib/jobs/ai-job-media-step.js +610 -0
  40. package/src/lib/jobs/ai-job-media-step.js.map +1 -0
  41. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  42. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  43. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  44. package/src/lib/jobs/ai-job-page-language.d.ts +4 -1
  45. package/src/lib/jobs/ai-job-page-language.js +30 -5
  46. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-page-sections.js +5 -4
  48. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-step.js +3 -2
  50. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  51. package/src/lib/jobs/ai-job-plan-step.d.ts +14 -3
  52. package/src/lib/jobs/ai-job-plan-step.js +87 -20
  53. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  55. package/src/lib/jobs/ai-job-products-step.js +2 -2
  56. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-settings-step.d.ts +268 -0
  58. package/src/lib/jobs/ai-job-settings-step.js +690 -0
  59. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  60. package/src/lib/jobs/ai-job-site-content.d.ts +22 -8
  61. package/src/lib/jobs/ai-job-site-content.js +33 -15
  62. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-datasets.d.ts +3 -1
  64. package/src/lib/jobs/ai-job-site-datasets.js +13 -5
  65. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  66. package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
  67. package/src/lib/jobs/ai-job-site-events.js +313 -0
  68. package/src/lib/jobs/ai-job-site-events.js.map +1 -0
  69. package/src/lib/jobs/ai-job-site-step.d.ts +12 -4
  70. package/src/lib/jobs/ai-job-site-step.js +132 -26
  71. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  72. package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
  73. package/src/lib/jobs/ai-job-tracking-step.js +501 -0
  74. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
  75. package/src/lib/jobs/ai-layout-stock-photos.d.ts +21 -0
  76. package/src/lib/jobs/ai-layout-stock-photos.js +41 -4
  77. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  78. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  79. package/src/lib/jobs/ai-site-publish.js +202 -56
  80. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  81. package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
  82. package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
  83. package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
  84. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
  85. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
  86. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
  87. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
  90. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -7
  91. package/src/lib/layout-language/ai-layout-compiler.js +62 -42
  92. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  93. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  94. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  95. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  96. package/src/lib/layout-language/ai-layout-frame.d.ts +24 -5
  97. package/src/lib/layout-language/ai-layout-frame.js +93 -20
  98. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  99. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
  102. package/src/lib/layout-language/ai-layout-labels.js +162 -0
  103. package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
  104. package/src/lib/layout-language/ai-layout-links.js +5 -2
  105. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  106. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  107. package/src/lib/layout-language/ai-layout-listings.js +66 -5
  108. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  109. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  110. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  111. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  112. package/src/lib/model/ai-build-event-page.d.ts +70 -0
  113. package/src/lib/model/ai-build-event-page.js +145 -0
  114. package/src/lib/model/ai-build-event-page.js.map +1 -0
  115. package/src/lib/model/ai-build-job.d.ts +44 -0
  116. package/src/lib/model/ai-build-job.js +141 -13
  117. package/src/lib/model/ai-build-job.js.map +1 -1
  118. package/src/lib/model/ai-build-plan.d.ts +39 -3
  119. package/src/lib/model/ai-build-plan.js +59 -5
  120. package/src/lib/model/ai-build-plan.js.map +1 -1
  121. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  122. package/src/lib/model/ai-credit-estimate.js +51 -0
  123. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  124. package/src/lib/model/ai-job-activity.js +6 -1
  125. package/src/lib/model/ai-job-activity.js.map +1 -1
  126. package/src/lib/model/ai-jobs.types.d.ts +18 -2
  127. package/src/lib/model/ai-jobs.types.js +6 -1
  128. package/src/lib/model/ai-jobs.types.js.map +1 -1
  129. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  130. package/src/lib/model/ai-layout-menus.js +518 -0
  131. package/src/lib/model/ai-layout-menus.js.map +1 -0
  132. package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
  133. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  134. package/src/lib/model/ai-products.d.ts +31 -1
  135. package/src/lib/model/ai-products.js +34 -1
  136. package/src/lib/model/ai-products.js.map +1 -1
  137. package/src/lib/model/ai-site-inventory.d.ts +17 -0
  138. package/src/lib/model/ai-site-inventory.js +5 -0
  139. package/src/lib/model/ai-site-inventory.js.map +1 -1
  140. package/src/lib/model/ai-site-job.d.ts +12 -2
  141. package/src/lib/model/ai-site-job.js +39 -12
  142. package/src/lib/model/ai-site-job.js.map +1 -1
  143. package/src/lib/model/ai-site-store-pages.js +1 -1
  144. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  145. package/src/lib/runtime/ai-doctrine-validators.d.ts +78 -3
  146. package/src/lib/runtime/ai-doctrine-validators.js +219 -31
  147. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  148. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  149. package/src/lib/runtime/ai-doctrine.js +16 -7
  150. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  151. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  152. package/src/lib/runtime/ai-eval-live.js +1 -1
  153. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  154. package/src/lib/runtime/ai-palette.d.ts +3 -1
  155. package/src/lib/runtime/ai-palette.generated.js +499 -11
  156. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  157. package/src/lib/runtime/ai-palette.js +5 -3
  158. package/src/lib/runtime/ai-palette.js.map +1 -1
  159. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  160. package/src/lib/runtime/ai-products-generation.js +12 -6
  161. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  162. package/src/lib/runtime/ai-template-examples.js +8 -2
  163. package/src/lib/runtime/ai-template-examples.js.map +1 -1
  164. package/src/lib/runtime/site-inventory.d.ts +12 -0
  165. package/src/lib/runtime/site-inventory.js +29 -1
  166. package/src/lib/runtime/site-inventory.js.map +1 -1
  167. package/src/lib/server.js +20 -0
  168. package/src/lib/server.js.map +1 -1
  169. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  170. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  171. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  172. package/src/lib/tools/ai-products-tool.js +15 -5
  173. package/src/lib/tools/ai-products-tool.js.map +1 -1
@@ -130,6 +130,11 @@ export interface AiDoctrineTreeContext extends AiNodeTreeContext {
130
130
  * to when the site has none for what it promises. Absent: none is known.
131
131
  */
132
132
  homeScreenIds?: readonly string[];
133
+ /**
134
+ * The home pages' own titles (AGL-3760): a link home under one of them says
135
+ * where it goes in the site's language, as "Inicio" does on a Spanish site.
136
+ */
137
+ homeLabels?: readonly string[];
133
138
  /**
134
139
  * The site's pages that place its form, by id (AGL-3729): where a link whose
135
140
  * words ask a visitor to get in touch (`aiAsksToGetInTouch`) does what it
@@ -266,7 +271,9 @@ export declare function detectLayoutRegions(tree: AiDoctrineTree, outputKind: Ai
266
271
  /**
267
272
  * Rule 3. A form on a page is a Form bound by id to a form built on the
268
273
  * Forms page — fields, validation, consent and routing kept in one place.
269
- * Loose fields, an unbound Form, and fields drawn inside a bound Form (which
274
+ * Loose fields — a Form Field, or any of MUI's answer elements (an Input,
275
+ * Select, Radio Group, Checkbox or Rating) — an unbound Form, and fields
276
+ * drawn inside a bound Form (which
270
277
  * the page never renders: the placed form's own design replaces them) are
271
278
  * refused. A form's own design is the one place fields are drawn.
272
279
  *
@@ -509,7 +516,7 @@ export declare function aiAsksToGetInTouch(label: string): boolean;
509
516
  * The root is never taken out. A tree with nothing to repair comes back as
510
517
  * the same object; a node that is changed is copied, never edited in place.
511
518
  */
512
- export declare function aiSettleUnrelatedScreenLinks<T extends AiDoctrineTree>(tree: T, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds'> & {
519
+ export declare function aiSettleUnrelatedScreenLinks<T extends AiDoctrineTree>(tree: T, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds' | 'homeLabels'> & {
513
520
  /** The page being checked, by every id it goes by: a link never goes to itself. */
514
521
  ownScreenIds?: readonly string[];
515
522
  }): T;
@@ -524,7 +531,7 @@ export declare function aiSettleUnrelatedScreenLinks<T extends AiDoctrineTree>(t
524
531
  * not say home is refused, with a re-ask to link the screen that does what it
525
532
  * says, or to leave the link out.
526
533
  */
527
- export declare function detectUnrelatedScreenLinks(tree: AiDoctrineTree, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds'>): AiDoctrineViolation[];
534
+ export declare function detectUnrelatedScreenLinks(tree: AiDoctrineTree, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds' | 'homeLabels'>): AiDoctrineViolation[];
528
535
  /**
529
536
  * Rule 10, for a link that goes nowhere (AGL-3072). A Button or a Screen Link
530
537
  * goes where its `screenId` or its `href` says, and those are the only
@@ -890,6 +897,38 @@ export declare function aiSettlePlanFormDatasets(plan: AiBuildPlan, inventory: A
890
897
  * so the rule still asks.
891
898
  */
892
899
  export declare function aiSettlePlanLayouts(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
900
+ /**
901
+ * A page section that is a layout region — a header, navigation or footer,
902
+ * read by the name rule 2's `plan-layout-region-section` reads — is taken off
903
+ * the page before the rules read the plan (AGL-3750). The region is already
904
+ * the layout's, so the rule's own answer, "take the section off the page", is
905
+ * the only one: a live Free start of 2026-10-12 (a bakery that sells cakes to
906
+ * order) planned a Header and a Footer on its pages three times and stopped.
907
+ *
908
+ * Only a section that places nothing, or nothing but a layout (which rule 2
909
+ * refuses placing as well), is taken off: one that places a form or a
910
+ * component may be a band misnamed, so the rule still asks. A page whose
911
+ * every section is a region keeps them all, and is re-asked as before, since
912
+ * a page of no sections has no answer here. A home this leaves under its
913
+ * fewest sections, or a Free plan still past its wall, is the existing
914
+ * checks' to settle or refuse.
915
+ */
916
+ export declare function aiSettlePlanLayoutRegionSections(plan: AiBuildPlan, inventory: AiSiteInventory | null): AiBuildPlan;
917
+ /**
918
+ * A section that shows the work — a gallery, a portfolio, selected works, by
919
+ * the name `aiSiteSectionShowsWork` reads — planned with fewer pieces than
920
+ * the job's `galleryMinItems` is raised to it before the rules read the plan
921
+ * (AGL-3754). The site start's `plan-empty-gallery` refusal names one answer,
922
+ * "plan it with three to six items", and the fewest is the one that keeps
923
+ * the plan's own count closest: a section's items move no section cap, Free
924
+ * wall or credit figure, which count sections and pages.
925
+ *
926
+ * A section the rule reads as one item of a list split into sections is
927
+ * left for rule 1's re-ask to join, as the Free wall's trim leaves it: raised
928
+ * here, it would hide the split. A job that sets no `galleryMinItems` (every
929
+ * job but a site start) is returned unchanged.
930
+ */
931
+ export declare function aiSettlePlanGalleries(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
893
932
  /** The contact form a plan is given when a section collects answers and the plan makes no form. */
894
933
  export declare const AI_PLAN_SETTLED_FORM: {
895
934
  readonly name: "Contact form";
@@ -1027,6 +1066,42 @@ export declare function aiFreePageSectionsWithin(creations: {
1027
1066
  layouts: number;
1028
1067
  pages?: number;
1029
1068
  }, credits?: Readonly<AiFreePageWorstCase>): number;
1069
+ /**
1070
+ * The most sections past the Free wall a plan is trimmed by rather than
1071
+ * re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
1072
+ * sections against a wall of 8 on all three answers, each with a home of 7:
1073
+ * the model reaching for the budget the site redo names ("plan 7 to 8") and
1074
+ * landing one band past it, which no re-ask cured. Within two, the plan's
1075
+ * shape is right and only a band or two the brief needs least is extra, so
1076
+ * taking those from the end loses nothing the plan depends on. Further over,
1077
+ * the shape itself is off — a list planned as sections, a page the wall
1078
+ * cannot pay for — and cutting more would drop content the model meant, so
1079
+ * that plan is still re-asked.
1080
+ */
1081
+ export declare const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
1082
+ /**
1083
+ * A Free plan a section or two past the wall is trimmed to fit before the
1084
+ * rules read the plan (AGL-3737), rather than re-asked: which sections go has
1085
+ * one answer, so the re-ask only costs the member a stopped start. The live
1086
+ * candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
1087
+ * 2 against a wall of 8, three answers running, and stopped needing input.
1088
+ *
1089
+ * One section goes at a time, the last of its page that `freeWallKeeps`
1090
+ * does not hold, from the page furthest past its share: the home at / past
1091
+ * its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
1092
+ * for a full home), any other page past two; on a tie, the longer page, then
1093
+ * the home. No home goes under its fewest and no page under one section. The
1094
+ * shares are what a site start is told to plan (a home of five or six, the
1095
+ * other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
1096
+ * 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
1097
+ * own redo chose.
1098
+ *
1099
+ * A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
1100
+ * start past its page cap, a plan with a list split into sections, and a
1101
+ * plan the trim cannot fit without a section it keeps are returned
1102
+ * unchanged, for the wall to re-ask.
1103
+ */
1104
+ export declare function aiSettlePlanFreeWall(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
1030
1105
  /**
1031
1106
  * The Free wall (plan): a plan asks for no more sections than the Free taste
1032
1107
  * pays for at its worst beside what the job builds first (AGL-3070). A live
@@ -15,6 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { sanitizeAuthorHtml } from "@aglyn/aglyn/app-utils/author-html";
18
+ import { isFormInputNode } from "@aglyn/aglyn/app-utils/forms";
18
19
  import { formatBytes, measureNodeMap, nodeMapBytes } from "@aglyn/aglyn/app-utils/measure-node-map";
19
20
  import { MEDIA_CDN_VARIANT_WIDTHS, parseMediaRef } from "@aglyn/aglyn/app-utils/media-ref";
20
21
  import { ESTIMATED_PAGE_TRANSFER_BYTES } from "@aglyn/aglyn/app-utils/plan-entitlements";
@@ -25,7 +26,9 @@ import { parseBreakpointSpan } from "@aglyn/shared-data-enums/breakpoint-span";
25
26
  import { renderEmailHtml } from "@aglyn/shared-util-email/email-render";
26
27
  import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_BUILD_PLAN_LIMITS, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, AI_PLAN_DATASET_ITEM_OP, aiPlanCreateFor, aiPlanFormDatasetNames, aiPlanItemFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
27
28
  import { aiCreationNoun, aiPlanCanPlaceForm, aiPlanUncreatable, aiPlanUncreatableKind } from "../model/ai-plan-capabilities.js";
28
- import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome } from "../model/ai-site-job.js";
29
+ import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome, aiSiteSectionShowsWork } from "../model/ai-site-job.js";
30
+ import { aiLayoutIsShopPage, aiLayoutShopGridSection } from "../layout-language/ai-layout-listings.js";
31
+ import { aiStorefrontSays } from "../layout-language/ai-layout-storefront.js";
29
32
  import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
30
33
  import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets } from "./ai-page-links.js";
31
34
  import { AI_EMAIL_CLIP_BYTES, AI_OUTPUT_BUDGETS, AI_OUTPUT_SURFACE, AI_TEXT_LIMITS, isHeadlineVariant } from "./ai-palette.js";
@@ -525,7 +528,9 @@ export const AI_LAYOUT_REGION_NAMES = Object.keys(AI_LAYOUT_REGIONS);
525
528
  /**
526
529
  * Rule 3. A form on a page is a Form bound by id to a form built on the
527
530
  * Forms page — fields, validation, consent and routing kept in one place.
528
- * Loose fields, an unbound Form, and fields drawn inside a bound Form (which
531
+ * Loose fields — a Form Field, or any of MUI's answer elements (an Input,
532
+ * Select, Radio Group, Checkbox or Rating) — an unbound Form, and fields
533
+ * drawn inside a bound Form (which
529
534
  * the page never renders: the placed form's own design replaces them) are
530
535
  * refused. A form's own design is the one place fields are drawn.
531
536
  *
@@ -541,7 +546,7 @@ export const AI_LAYOUT_REGION_NAMES = Object.keys(AI_LAYOUT_REGIONS);
541
546
  const unbound = [];
542
547
  const shadowed = [];
543
548
  for (const visit of walkTree(tree)){
544
- if (visit.node.componentId === 'formField' && !hasAncestor(tree, visit, (node)=>node.componentId === 'form')) {
549
+ if (isFormInputNode(visit.node) && !hasAncestor(tree, visit, (node)=>node.componentId === 'form')) {
545
550
  loose.push(visit.id);
546
551
  }
547
552
  if (visit.node.componentId === 'form') {
@@ -1841,6 +1846,7 @@ const GRID = 'muiGrid';
1841
1846
  ];
1842
1847
  }
1843
1848
  /** A link's words that say it goes home. */ const HOME_WORDS = /\bhome(?:\s?page)?\b/i;
1849
+ /** A label's words, lowercase, for comparing one label with another. */ const labelWords = (label)=>label.toLowerCase().split(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+", "u")).filter(Boolean).join(' ');
1844
1850
  /** Words that ask a visitor to get in touch, which the site's form answers. */ const GET_IN_TOUCH_WORDS = /\b(?:contact|book|booking|call|quote|estimate|appointment|schedule|reserve|reservation|get in touch|request|visit|enquire|inquire|enquiry|inquiry|message|talk|consult|consultation|order|pre-?order|sign up|join|apply|start)\b/i;
1845
1851
  /**
1846
1852
  * Whether a link's words ask a visitor to get in touch — contact, book, quote,
@@ -1854,10 +1860,11 @@ const GRID = 'muiGrid';
1854
1860
  * in document order. A link whose words ask a visitor to get in touch, sent to
1855
1861
  * a home page that places the site's form, does what it says (AGL-3729).
1856
1862
  */ function unrelatedScreenLinks(tree, outputKind, context) {
1857
- var _context_homeScreenIds, _context_formScreenIds;
1863
+ var _context_homeScreenIds, _context_formScreenIds, _context_homeLabels;
1858
1864
  if (outputKind === 'email' || outputKind === 'form') return [];
1859
1865
  const home = new Set((_context_homeScreenIds = context.homeScreenIds) != null ? _context_homeScreenIds : []);
1860
1866
  const forms = new Set((_context_formScreenIds = context.formScreenIds) != null ? _context_formScreenIds : []);
1867
+ const homeLabels = new Set(((_context_homeLabels = context.homeLabels) != null ? _context_homeLabels : []).map(labelWords).filter(Boolean));
1861
1868
  const sent = [];
1862
1869
  for (const visit of walkTree(tree)){
1863
1870
  var _node_props, _node_props1, _node_props2;
@@ -1866,7 +1873,7 @@ const GRID = 'muiGrid';
1866
1873
  const screenId = (_node_props = node.props) == null ? void 0 : _node_props['screenId'];
1867
1874
  const linksHome = typeof screenId === 'string' && home.has(screenId) || ((_node_props1 = node.props) == null ? void 0 : _node_props1['href']) === '/';
1868
1875
  const label = typeof ((_node_props2 = node.props) == null ? void 0 : _node_props2['children']) === 'string' ? node.props['children'].trim() : '';
1869
- if (!linksHome || !label || HOME_WORDS.test(label)) continue;
1876
+ if (!linksHome || !label || HOME_WORDS.test(label) || homeLabels.has(labelWords(label))) continue;
1870
1877
  // Home holds the site's form, and the words ask for what it does (AGL-3729).
1871
1878
  if (typeof screenId === 'string' && forms.has(screenId) && aiAsksToGetInTouch(label)) continue;
1872
1879
  // The header and the navigation link home by name and by the site's own brand.
@@ -3286,7 +3293,10 @@ const BUDGET_METRICS = [
3286
3293
  };
3287
3294
  const violations = [
3288
3295
  ...publish,
3289
- ...detectRepeatedSubtrees(tree, otherPages, context),
3296
+ // A form's fields share a shape because a field IS one shape — a Form
3297
+ // Control over a Form Label and an answer — and a form places no
3298
+ // instances, so rule 1 has nothing to ask of one.
3299
+ ...outputKind === 'form' ? [] : detectRepeatedSubtrees(tree, otherPages, context),
3290
3300
  ...detectLayoutRegions(tree, outputKind),
3291
3301
  ...detectInlineForms(tree, outputKind),
3292
3302
  ...detectLiteralStyles(tree, outputKind),
@@ -3769,6 +3779,76 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
3769
3779
  }))
3770
3780
  });
3771
3781
  }
3782
+ /**
3783
+ * A page section that is a layout region — a header, navigation or footer,
3784
+ * read by the name rule 2's `plan-layout-region-section` reads — is taken off
3785
+ * the page before the rules read the plan (AGL-3750). The region is already
3786
+ * the layout's, so the rule's own answer, "take the section off the page", is
3787
+ * the only one: a live Free start of 2026-10-12 (a bakery that sells cakes to
3788
+ * order) planned a Header and a Footer on its pages three times and stopped.
3789
+ *
3790
+ * Only a section that places nothing, or nothing but a layout (which rule 2
3791
+ * refuses placing as well), is taken off: one that places a form or a
3792
+ * component may be a band misnamed, so the rule still asks. A page whose
3793
+ * every section is a region keeps them all, and is re-asked as before, since
3794
+ * a page of no sections has no answer here. A home this leaves under its
3795
+ * fewest sections, or a Free plan still past its wall, is the existing
3796
+ * checks' to settle or refuse.
3797
+ */ export function aiSettlePlanLayoutRegionSections(plan, inventory) {
3798
+ const kinds = inventoryKinds(inventory);
3799
+ const removable = (section)=>LAYOUT_REGION_NAME.test(section.name) && section.uses.every((ref)=>refKind(ref, plan, kinds) === 'layout');
3800
+ let changed = false;
3801
+ const screens = plan.screens.map((screen)=>{
3802
+ const kept = screen.sections.filter((section)=>!removable(section));
3803
+ if (kept.length === screen.sections.length || !kept.length) return screen;
3804
+ changed = true;
3805
+ return _extends({}, screen, {
3806
+ sections: kept
3807
+ });
3808
+ });
3809
+ return changed ? _extends({}, plan, {
3810
+ screens
3811
+ }) : plan;
3812
+ }
3813
+ /**
3814
+ * A section that shows the work — a gallery, a portfolio, selected works, by
3815
+ * the name `aiSiteSectionShowsWork` reads — planned with fewer pieces than
3816
+ * the job's `galleryMinItems` is raised to it before the rules read the plan
3817
+ * (AGL-3754). The site start's `plan-empty-gallery` refusal names one answer,
3818
+ * "plan it with three to six items", and the fewest is the one that keeps
3819
+ * the plan's own count closest: a section's items move no section cap, Free
3820
+ * wall or credit figure, which count sections and pages.
3821
+ *
3822
+ * A section the rule reads as one item of a list split into sections is
3823
+ * left for rule 1's re-ask to join, as the Free wall's trim leaves it: raised
3824
+ * here, it would hide the split. A job that sets no `galleryMinItems` (every
3825
+ * job but a site start) is returned unchanged.
3826
+ */ export function aiSettlePlanGalleries(plan, inventory, capabilities = null) {
3827
+ var _ref;
3828
+ const least = (_ref = capabilities == null ? void 0 : capabilities.galleryMinItems) != null ? _ref : 0;
3829
+ const thin = (section)=>aiSiteSectionShowsWork(section.name) && section.items < least;
3830
+ if (!plan.screens.some((screen)=>screen.sections.some(thin))) return plan;
3831
+ const split = new Set(detectPlanSplitLists(plan, inventory, capabilities).flatMap((violation)=>{
3832
+ var _violation_paths;
3833
+ return (_violation_paths = violation.paths) != null ? _violation_paths : [];
3834
+ }));
3835
+ let changed = false;
3836
+ const screens = plan.screens.map((screen, screenIndex)=>{
3837
+ if (!screen.sections.some(thin)) return screen;
3838
+ return _extends({}, screen, {
3839
+ sections: screen.sections.map((section, sectionIndex)=>{
3840
+ if (!thin(section) || split.has(`screens[${screenIndex}].sections[${sectionIndex}]`)) return section;
3841
+ changed = true;
3842
+ return _extends({}, section, {
3843
+ items: least
3844
+ });
3845
+ })
3846
+ });
3847
+ });
3848
+ return changed ? _extends({}, plan, {
3849
+ screens
3850
+ }) : plan;
3851
+ }
3772
3852
  /** Every saved form a plan could place, the plan's own creations first. */ function planFormChoices(plan, inventory) {
3773
3853
  var _ref;
3774
3854
  return [
@@ -4419,6 +4499,135 @@ function slugKey(slug) {
4419
4499
  const needs = !((_ref = inventory == null ? void 0 : inventory.layouts.length) != null ? _ref : 0) && capabilities.create.layout.allowed ? 1 : 0;
4420
4500
  return Math.max(creates, needs);
4421
4501
  }
4502
+ /** The Free wall a plan is held to, or null where the job is not on the Free taste. */ function freePlanWall(plan, inventory, capabilities) {
4503
+ if (!(capabilities == null ? void 0 : capabilities.freeTaste)) return null;
4504
+ const asked = plan.screens.reduce((sum, screen)=>sum + screen.sections.length, 0);
4505
+ const layouts = freePlanLayouts(plan, inventory, capabilities);
4506
+ const pages = Math.max(plan.screens.length, 1);
4507
+ // A Free site scaffold (AGL-3594) is held to its page cap first, and then to
4508
+ // what a SITE plan's worst case leaves for sections, with room for a retry.
4509
+ const sitePages = capabilities.freeSitePages;
4510
+ const overPages = sitePages !== undefined && plan.screens.length > sitePages;
4511
+ // A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
4512
+ const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
4513
+ const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
4514
+ layouts,
4515
+ pages,
4516
+ forms: Math.max(0, forms)
4517
+ }, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
4518
+ layouts,
4519
+ pages
4520
+ });
4521
+ // A site start's home keeps its full five (AGL-3660): told only "plan at
4522
+ // most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
4523
+ // its other page, so the redo names where the sections come from.
4524
+ const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
4525
+ pages,
4526
+ across: fits
4527
+ }) : 0;
4528
+ return {
4529
+ asked,
4530
+ fits,
4531
+ layouts,
4532
+ pages,
4533
+ sitePages,
4534
+ overPages,
4535
+ homeMin
4536
+ };
4537
+ }
4538
+ /**
4539
+ * The most sections past the Free wall a plan is trimmed by rather than
4540
+ * re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
4541
+ * sections against a wall of 8 on all three answers, each with a home of 7:
4542
+ * the model reaching for the budget the site redo names ("plan 7 to 8") and
4543
+ * landing one band past it, which no re-ask cured. Within two, the plan's
4544
+ * shape is right and only a band or two the brief needs least is extra, so
4545
+ * taking those from the end loses nothing the plan depends on. Further over,
4546
+ * the shape itself is off — a list planned as sections, a page the wall
4547
+ * cannot pay for — and cutting more would drop content the model meant, so
4548
+ * that plan is still re-asked.
4549
+ */ export const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
4550
+ /** A section through which a visitor reaches the owner: contact, a visit, a booking. */ const REACH_SECTION_NAME = /\b(?:contact|get in touch|visit(?: us)?|find us|locations?|hours|book(?:ing)?|reserve|reservations?)\b/i;
4551
+ /**
4552
+ * Whether the Free wall's trim keeps a section of a page (AGL-3737): the
4553
+ * page's opening, any section that places something (a dataset it lists, a
4554
+ * form, a component — dropping it would leave a creation unplaced), one that
4555
+ * collects answers or says how to reach the owner, and a storefront's
4556
+ * listing parts — the bestsellers, the collections, the sign-up, and a Shop
4557
+ * page's product grid.
4558
+ */ function freeWallKeeps(screen, index) {
4559
+ const section = screen.sections[index];
4560
+ if (index === 0 || section.uses.length > 0) return true;
4561
+ if (FORM_SECTION_NAME.test(section.name) || REACH_SECTION_NAME.test(section.name)) return true;
4562
+ if (aiStorefrontSays('featured', section.name) || aiStorefrontSays('collections', section.name) || aiStorefrontSays('signup', section.name)) {
4563
+ return true;
4564
+ }
4565
+ return aiLayoutIsShopPage(screen) && aiLayoutShopGridSection(screen.sections) === index;
4566
+ }
4567
+ /**
4568
+ * A Free plan a section or two past the wall is trimmed to fit before the
4569
+ * rules read the plan (AGL-3737), rather than re-asked: which sections go has
4570
+ * one answer, so the re-ask only costs the member a stopped start. The live
4571
+ * candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
4572
+ * 2 against a wall of 8, three answers running, and stopped needing input.
4573
+ *
4574
+ * One section goes at a time, the last of its page that `freeWallKeeps`
4575
+ * does not hold, from the page furthest past its share: the home at / past
4576
+ * its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
4577
+ * for a full home), any other page past two; on a tie, the longer page, then
4578
+ * the home. No home goes under its fewest and no page under one section. The
4579
+ * shares are what a site start is told to plan (a home of five or six, the
4580
+ * other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
4581
+ * 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
4582
+ * own redo chose.
4583
+ *
4584
+ * A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
4585
+ * start past its page cap, a plan with a list split into sections, and a
4586
+ * plan the trim cannot fit without a section it keeps are returned
4587
+ * unchanged, for the wall to re-ask.
4588
+ */ export function aiSettlePlanFreeWall(plan, inventory, capabilities = null) {
4589
+ const wall = freePlanWall(plan, inventory, capabilities);
4590
+ if (!wall || wall.overPages) return plan;
4591
+ const over = wall.asked - wall.fits;
4592
+ if (over <= 0 || over > AI_FREE_WALL_SETTLE_MAX_OVER) return plan;
4593
+ // A list split into sections is joined by its re-ask, which frees the room;
4594
+ // a trim would cut one of its items and hide the split from the rule.
4595
+ if (detectPlanSplitLists(plan, inventory, capabilities).length) return plan;
4596
+ const kept = plan.screens.map((screen)=>screen.sections.map((section, index)=>({
4597
+ section,
4598
+ index
4599
+ })));
4600
+ const home = plan.screens.findIndex(aiSitePlanIsHome);
4601
+ const floor = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 1;
4602
+ const target = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 2;
4603
+ // The last section of a page the trim may take, or -1.
4604
+ const last = (screenIndex)=>{
4605
+ const entries = kept[screenIndex];
4606
+ if (entries.length <= floor(screenIndex)) return -1;
4607
+ for(let at = entries.length - 1; at > 0; at -= 1){
4608
+ if (!freeWallKeeps(plan.screens[screenIndex], entries[at].index)) return at;
4609
+ }
4610
+ return -1;
4611
+ };
4612
+ for(let left = over; left > 0; left -= 1){
4613
+ let pick = -1;
4614
+ for(let index = 0; index < kept.length; index += 1){
4615
+ if (last(index) === -1) continue;
4616
+ const room = kept[index].length - target(index);
4617
+ const best = pick === -1 ? null : kept[pick].length - target(pick);
4618
+ if (best === null || room > best || room === best && (kept[index].length > kept[pick].length || kept[index].length === kept[pick].length && index === home)) {
4619
+ pick = index;
4620
+ }
4621
+ }
4622
+ if (pick === -1) return plan;
4623
+ kept[pick].splice(last(pick), 1);
4624
+ }
4625
+ return _extends({}, plan, {
4626
+ screens: plan.screens.map((screen, index)=>kept[index].length === screen.sections.length ? screen : _extends({}, screen, {
4627
+ sections: kept[index].map((entry)=>entry.section)
4628
+ }))
4629
+ });
4630
+ }
4422
4631
  /**
4423
4632
  * The Free wall (plan): a plan asks for no more sections than the Free taste
4424
4633
  * pays for at its worst beside what the job builds first (AGL-3070). A live
@@ -4429,14 +4638,10 @@ function slugKey(slug) {
4429
4638
  * proven with, so no plan the proof admits is refused. It is no building rule,
4430
4639
  * so it names none.
4431
4640
  */ export function detectPlanOverFreeWall(plan, inventory, capabilities) {
4432
- if (!(capabilities == null ? void 0 : capabilities.freeTaste)) return [];
4433
- const asked = plan.screens.reduce((sum, screen)=>sum + screen.sections.length, 0);
4434
- const layouts = freePlanLayouts(plan, inventory, capabilities);
4435
- const pages = Math.max(plan.screens.length, 1);
4436
- // A Free site scaffold (AGL-3594) is held to its page cap first, and then to
4437
- // what a SITE plan's worst case leaves for sections, with room for a retry.
4438
- const sitePages = capabilities.freeSitePages;
4439
- if (sitePages !== undefined && plan.screens.length > sitePages) {
4641
+ const wall = freePlanWall(plan, inventory, capabilities);
4642
+ if (!wall) return [];
4643
+ const { asked, layouts, pages, sitePages, fits, homeMin } = wall;
4644
+ if (sitePages !== undefined && wall.overPages) {
4440
4645
  return [
4441
4646
  {
4442
4647
  rule: null,
@@ -4448,26 +4653,9 @@ function slugKey(slug) {
4448
4653
  }
4449
4654
  ];
4450
4655
  }
4451
- // A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
4452
- const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
4453
- const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
4454
- layouts,
4455
- pages,
4456
- forms: Math.max(0, forms)
4457
- }, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
4458
- layouts,
4459
- pages
4460
- });
4461
4656
  if (asked <= fits) return [];
4462
4657
  const job = pages > 1 ? `a Free plan of ${pages} pages` : 'a Free page';
4463
4658
  const beside = layouts ? ` that creates ${layouts > 1 ? `${layouts} layouts` : 'its layout'}` : '';
4464
- // A site start's home keeps its full five (AGL-3660): told only "plan at
4465
- // most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
4466
- // its other page, so the redo names where the sections come from.
4467
- const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
4468
- pages,
4469
- across: fits
4470
- }) : 0;
4471
4659
  const keepHome = homeMin ? ` Keep the home page at / at ${homeMin} or more sections, and take the rest from the other ${pages > 2 ? 'pages' : 'page'}.` : '';
4472
4660
  // A site start's redo keeps the plan full (AGL-3660): the wall's sections
4473
4661
  // are its budget to use, and a redo told only "at most" cuts far under it.