@aglyn/plugins-ai 1.0.0-beta.237 → 1.0.0-beta.239

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 (172) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
  3. package/src/lib/components/ai-credits-prompt.component.js +84 -34
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-links.js +8 -0
  9. package/src/lib/components/ai-job-links.js.map +1 -1
  10. package/src/lib/components/ai-job-plan.component.js +12 -1
  11. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  12. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  13. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  14. package/src/lib/components/ai-jobs-store.d.ts +12 -0
  15. package/src/lib/components/ai-jobs-store.js +15 -0
  16. package/src/lib/components/ai-jobs-store.js.map +1 -1
  17. package/src/lib/components/ai-site-build-page.component.js +22 -2
  18. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  19. package/src/lib/components/ai-usage-meter-refresh.d.ts +35 -0
  20. package/src/lib/components/ai-usage-meter-refresh.js +63 -0
  21. package/src/lib/components/ai-usage-meter-refresh.js.map +1 -0
  22. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  23. package/src/lib/components/ai-usage-strip.component.js +40 -9
  24. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  25. package/src/lib/components/assist-jobs-drawer.component.js +12 -16
  26. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  27. package/src/lib/components/assist-panel.component.js +91 -51
  28. package/src/lib/components/assist-panel.component.js.map +1 -1
  29. package/src/lib/components/use-ai-usage-meter.d.ts +2 -0
  30. package/src/lib/components/use-ai-usage-meter.js +8 -3
  31. package/src/lib/components/use-ai-usage-meter.js.map +1 -1
  32. package/src/lib/jobs/ai-build-unit-outcome.d.ts +3 -1
  33. package/src/lib/jobs/ai-build-unit-outcome.js +19 -3
  34. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  35. package/src/lib/jobs/ai-job-build-step.js +33 -3
  36. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  38. package/src/lib/jobs/ai-job-cancel.js +102 -0
  39. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  40. package/src/lib/jobs/ai-job-component-step.js +4 -1
  41. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-draft-ids.js +7 -2
  43. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  44. package/src/lib/jobs/ai-job-drafts.js +2 -0
  45. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  46. package/src/lib/jobs/ai-job-form-dataset.d.ts +100 -0
  47. package/src/lib/jobs/ai-job-form-dataset.js +161 -0
  48. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -0
  49. package/src/lib/jobs/ai-job-form-step.d.ts +15 -3
  50. package/src/lib/jobs/ai-job-form-step.js +41 -10
  51. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  53. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  54. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  56. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  58. package/src/lib/jobs/ai-job-page-language.js +45 -2
  59. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  60. package/src/lib/jobs/ai-job-page-step.js +12 -1
  61. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-plan-step.d.ts +38 -1
  63. package/src/lib/jobs/ai-job-plan-step.js +90 -13
  64. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
  66. package/src/lib/jobs/ai-job-site-content.js +66 -7
  67. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  68. package/src/lib/jobs/ai-job-site-datasets.d.ts +170 -0
  69. package/src/lib/jobs/ai-job-site-datasets.js +499 -0
  70. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -0
  71. package/src/lib/jobs/ai-job-site-step.d.ts +15 -8
  72. package/src/lib/jobs/ai-job-site-step.js +152 -30
  73. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  74. package/src/lib/jobs/ai-job-template-step.js +4 -1
  75. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  76. package/src/lib/jobs/ai-jobs.d.ts +37 -12
  77. package/src/lib/jobs/ai-jobs.js +184 -28
  78. package/src/lib/jobs/ai-jobs.js.map +1 -1
  79. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
  80. package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
  81. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  82. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  83. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  84. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  85. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.d.ts +30 -0
  86. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js +637 -0
  87. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js.map +1 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  90. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  91. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -0
  92. package/src/lib/layout-language/ai-layout-compiler.js +397 -9
  93. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  95. package/src/lib/layout-language/ai-layout-design.js +5 -3
  96. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  98. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  99. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js +17 -1
  101. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  102. package/src/lib/layout-language/ai-layout-listings.d.ts +44 -3
  103. package/src/lib/layout-language/ai-layout-listings.js +118 -50
  104. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  105. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  106. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  108. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  109. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  110. package/src/lib/model/ai-build-job.d.ts +11 -1
  111. package/src/lib/model/ai-build-job.js +35 -6
  112. package/src/lib/model/ai-build-job.js.map +1 -1
  113. package/src/lib/model/ai-build-plan.d.ts +23 -3
  114. package/src/lib/model/ai-build-plan.js +47 -6
  115. package/src/lib/model/ai-build-plan.js.map +1 -1
  116. package/src/lib/model/ai-build-progress.d.ts +2 -2
  117. package/src/lib/model/ai-build-progress.js +6 -4
  118. package/src/lib/model/ai-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-credit-estimate.d.ts +69 -2
  120. package/src/lib/model/ai-credit-estimate.js +91 -3
  121. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  122. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  123. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  124. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  125. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  126. package/src/lib/model/ai-job-failure-copy.js +8 -3
  127. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  128. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  129. package/src/lib/model/ai-jobs.types.js.map +1 -1
  130. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  131. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  132. package/src/lib/model/ai-site-build-progress.js +7 -4
  133. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  134. package/src/lib/model/ai-site-job.d.ts +38 -4
  135. package/src/lib/model/ai-site-job.js +50 -14
  136. package/src/lib/model/ai-site-job.js.map +1 -1
  137. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  138. package/src/lib/model/ai-site-kinds.js +34 -6
  139. package/src/lib/model/ai-site-kinds.js.map +1 -1
  140. package/src/lib/runtime/ai-dataset-generation.d.ts +77 -0
  141. package/src/lib/runtime/ai-dataset-generation.js +127 -0
  142. package/src/lib/runtime/ai-dataset-generation.js.map +1 -0
  143. package/src/lib/runtime/ai-doctrine-validators.d.ts +77 -1
  144. package/src/lib/runtime/ai-doctrine-validators.js +280 -6
  145. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  146. package/src/lib/runtime/ai-doctrine.js +10 -4
  147. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  148. package/src/lib/runtime/ai-node-tree.d.ts +2 -0
  149. package/src/lib/runtime/ai-node-tree.js +16 -0
  150. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  151. package/src/lib/runtime/ai-palette.generated.js +53 -7
  152. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  153. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  154. package/src/lib/server/ai-jobs-cancel.js +27 -9
  155. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  156. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  157. package/src/lib/server/ai-jobs-gate.js +20 -9
  158. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  159. package/src/lib/tools/ai-dataset-tool.d.ts +68 -0
  160. package/src/lib/tools/ai-dataset-tool.js +231 -0
  161. package/src/lib/tools/ai-dataset-tool.js.map +1 -0
  162. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  163. package/src/lib/usage/ai-usage-meter.js +17 -1
  164. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  165. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  166. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  167. package/src/lib/usage/assist-usage.d.ts +7 -0
  168. package/src/lib/usage/assist-usage.js +14 -4
  169. package/src/lib/usage/assist-usage.js.map +1 -1
  170. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  171. package/src/lib/usage/free-ai-credits-left.js +30 -4
  172. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A STORE'S STOREFRONT (AGL-3676, second round).
19
+ *
20
+ * Zach, 2026-10-09, on the beta.237 Willow Wick Candles start (job
21
+ * MtlXWjvt9P): "again this … site does not look like a store front". Its home
22
+ * was a lifestyle hero, ONE row of featured products and four text-only bands
23
+ * ("Why soy, why small batch", "How our candles are made", "Made to be
24
+ * loved", "Gifting and shipping"); its Shop page opened with a tall hero and
25
+ * the grid below the fold.
26
+ *
27
+ * A designer storefront (Shopify's Dawn, Craft, Sense; Squarespace's commerce
28
+ * templates) is composed of a handful of PARTS, in a familiar order: a hero
29
+ * that sells, the store's bestsellers, its collections as picture tiles, the
30
+ * brand's story beside a photo, why to buy here with icons, what customers
31
+ * say, and a newsletter sign-up. This module names those parts by the words a
32
+ * plan gives its sections, so the plan step can make sure a store's home has
33
+ * them and the compiler and the listings can draw each one its way.
34
+ *
35
+ * It is NOT a section library (Zach 10/7): the model still names, orders and
36
+ * writes every section, and designs each in the layout language; what code
37
+ * adds is a part the plan left out — named in plain words for the model to
38
+ * design — and the platform elements that show the store's own records.
39
+ *
40
+ * Pure: names and plans only.
41
+ */
42
+ import type { AiBuildPlanScreen } from '../model/ai-build-plan';
43
+ /** A part a storefront home is composed of. */
44
+ export type AiStorefrontPart = 'featured' | 'collections' | 'story' | 'values' | 'reviews' | 'signup';
45
+ /** The section names that say each part, as a plan or a page heading words it. */
46
+ export declare const AI_STOREFRONT_PART_WORDS: Readonly<Record<AiStorefrontPart, RegExp>>;
47
+ /** Whether a planned section's name says a part. */
48
+ export declare function aiStorefrontSays(part: AiStorefrontPart, name: string): boolean;
49
+ /**
50
+ * A store plan's screens with the storefront a shopper expects (AGL-3676):
51
+ *
52
+ * - THE HOME (paid starts only — the Free wall prices each section, and a
53
+ * Free store sells nothing yet): its hero first, then any part the plan
54
+ * left out, added under plain names while the page has room (eight
55
+ * sections), in order of need: the bestsellers the platform lists,
56
+ * right after the hero; the collections as picture tiles after them; the
57
+ * newsletter sign-up as the page's last band; and customer reviews (the
58
+ * store's real ones, which show only once shoppers leave them) before
59
+ * the close.
60
+ * - THE SHOP PAGE: grid first. Its product grid section moves to second,
61
+ * straight under the page's short title, so the products open above the
62
+ * fold; anything the plan put between them (an intro, a story) follows
63
+ * the grid.
64
+ *
65
+ * Every section the plan named stays, in its order otherwise; nothing is
66
+ * removed, and a page at eight sections is given nothing more.
67
+ */
68
+ export declare function aiStorefrontPlanScreens<S extends Pick<AiBuildPlanScreen, 'slug' | 'title' | 'sections'>>(screens: readonly S[], options: {
69
+ paid: boolean;
70
+ isShopPage: (screen: S) => boolean;
71
+ gridSection: (sections: S['sections']) => number;
72
+ }): S[];
73
+ /**
74
+ * The icon each of a store's reasons to buy shows (AGL-3676): its own where
75
+ * the design named one, else one its words suggest, else the next of a few
76
+ * neutral ones — never the same twice in one row.
77
+ */
78
+ export declare function aiStorefrontItemIcons(items: ReadonlyArray<{
79
+ title: string;
80
+ text: string;
81
+ icon?: string;
82
+ }>, known: (word: string | undefined) => boolean): string[];
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * A STORE'S STOREFRONT (AGL-3676, second round).
18
+ *
19
+ * Zach, 2026-10-09, on the beta.237 Willow Wick Candles start (job
20
+ * MtlXWjvt9P): "again this … site does not look like a store front". Its home
21
+ * was a lifestyle hero, ONE row of featured products and four text-only bands
22
+ * ("Why soy, why small batch", "How our candles are made", "Made to be
23
+ * loved", "Gifting and shipping"); its Shop page opened with a tall hero and
24
+ * the grid below the fold.
25
+ *
26
+ * A designer storefront (Shopify's Dawn, Craft, Sense; Squarespace's commerce
27
+ * templates) is composed of a handful of PARTS, in a familiar order: a hero
28
+ * that sells, the store's bestsellers, its collections as picture tiles, the
29
+ * brand's story beside a photo, why to buy here with icons, what customers
30
+ * say, and a newsletter sign-up. This module names those parts by the words a
31
+ * plan gives its sections, so the plan step can make sure a store's home has
32
+ * them and the compiler and the listings can draw each one its way.
33
+ *
34
+ * It is NOT a section library (Zach 10/7): the model still names, orders and
35
+ * writes every section, and designs each in the layout language; what code
36
+ * adds is a part the plan left out — named in plain words for the model to
37
+ * design — and the platform elements that show the store's own records.
38
+ *
39
+ * Pure: names and plans only.
40
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
41
+ /** The section names that say each part, as a plan or a page heading words it. */ export const AI_STOREFRONT_PART_WORDS = {
42
+ featured: /\b(bestsellers?|best[- ]sellers?|best[- ]selling|featured|new arrivals?|arrivals|just in|favou?rites?|signature|most loved|top picks?|our (?:candles|products|picks)|shop (?:all|now|the))\b/i,
43
+ collections: /\b(collections?|categor(?:y|ies)|shop by|browse by|by scent|scents?|ranges?|families|occasions?|edits?)\b/i,
44
+ story: /\b(story|about|our craft|craft|making|made|maker|process|how (?:we|it)|behind|studio|workshop)\b/i,
45
+ values: /\b(why|promise|promises|values?|difference|shipping|returns?|guarantee|perks?|benefits?|care|small[- ]batch|ingredients?|sustainab\w*)\b/i,
46
+ reviews: /\b(reviews?|testimonials?|customers? (?:say|love)|what (?:people|customers|our customers) say|kind words|praise|loved by|social proof)\b/i,
47
+ signup: /\b(newsletter|subscribe|sign[- ]?up|mailing list|join (?:us|the list|our list)|stay in touch|in the loop|first to know|inbox|letters? from)\b/i
48
+ };
49
+ /** Whether a planned section's name says a part. */ export function aiStorefrontSays(part, name) {
50
+ return AI_STOREFRONT_PART_WORDS[part].test(name);
51
+ }
52
+ /** The plan entry a part the plan left out is added as: plain words the page step designs. */ const ADDED = {
53
+ featured: {
54
+ name: 'Bestsellers',
55
+ uses: [],
56
+ items: 4
57
+ },
58
+ collections: {
59
+ name: 'Shop by collection',
60
+ uses: [],
61
+ items: 3
62
+ },
63
+ reviews: {
64
+ name: 'Customer reviews',
65
+ uses: [],
66
+ items: 0
67
+ },
68
+ signup: {
69
+ name: 'Newsletter sign-up',
70
+ uses: [],
71
+ items: 0
72
+ }
73
+ };
74
+ /** The most sections one page of a site start holds (`AI_SITE_MAX_SECTIONS`). */ const MOST_SECTIONS = 8;
75
+ const firstSegment = (slug)=>slug.trim().replace(/^\/+/, '').split('/')[0].toLowerCase();
76
+ /** A section that closes a page: its call to action, a contact or a sign-up band. */ const CLOSING = /\b(cta|call to action|closing|close|contact|get in touch|visit)\b/i;
77
+ /**
78
+ * A store plan's screens with the storefront a shopper expects (AGL-3676):
79
+ *
80
+ * - THE HOME (paid starts only — the Free wall prices each section, and a
81
+ * Free store sells nothing yet): its hero first, then any part the plan
82
+ * left out, added under plain names while the page has room (eight
83
+ * sections), in order of need: the bestsellers the platform lists,
84
+ * right after the hero; the collections as picture tiles after them; the
85
+ * newsletter sign-up as the page's last band; and customer reviews (the
86
+ * store's real ones, which show only once shoppers leave them) before
87
+ * the close.
88
+ * - THE SHOP PAGE: grid first. Its product grid section moves to second,
89
+ * straight under the page's short title, so the products open above the
90
+ * fold; anything the plan put between them (an intro, a story) follows
91
+ * the grid.
92
+ *
93
+ * Every section the plan named stays, in its order otherwise; nothing is
94
+ * removed, and a page at eight sections is given nothing more.
95
+ */ export function aiStorefrontPlanScreens(screens, options) {
96
+ return screens.map((screen)=>{
97
+ if (firstSegment(screen.slug) === '') return options.paid ? storefrontHome(screen) : screen;
98
+ if (!options.isShopPage(screen) || screen.sections.length < 3) return screen;
99
+ const grid = options.gridSection(screen.sections);
100
+ if (grid <= 1) return screen;
101
+ const sections = [
102
+ ...screen.sections
103
+ ];
104
+ const [moved] = sections.splice(grid, 1);
105
+ sections.splice(1, 0, moved);
106
+ return _extends({}, screen, {
107
+ sections
108
+ });
109
+ });
110
+ }
111
+ function storefrontHome(screen) {
112
+ const sections = [
113
+ ...screen.sections
114
+ ];
115
+ if (!sections.length) return screen;
116
+ // A section about the collections that also names the bestsellers is the bestsellers'.
117
+ const says = (part, name)=>aiStorefrontSays(part, name) && (part !== 'collections' || !aiStorefrontSays('featured', name));
118
+ const has = (part)=>sections.some((section, index)=>index > 0 && says(part, section.name));
119
+ const room = ()=>sections.length < MOST_SECTIONS;
120
+ // What a shopper meets first matters most, so the parts are added in that
121
+ // order while the page has room: the bestsellers, the collections, the
122
+ // sign-up, then the reviews — which show nothing until a shopper writes one.
123
+ if (!has('featured') && room()) sections.splice(1, 0, _extends({}, ADDED.featured));
124
+ if (!has('collections') && room()) {
125
+ // The collections follow the bestsellers.
126
+ const featured = sections.findIndex((section, index)=>index > 0 && says('featured', section.name));
127
+ sections.splice(featured > 0 ? featured + 1 : 1, 0, _extends({}, ADDED.collections));
128
+ }
129
+ // The newsletter is the page's last band, under any closing call to action.
130
+ if (!has('signup') && room()) sections.push(_extends({}, ADDED.signup));
131
+ if (!has('reviews') && room()) {
132
+ // The reviews come just before the page closes: above a closing sign-up
133
+ // and above a closing call to action.
134
+ let at = sections.length;
135
+ if (at - 1 > 0 && says('signup', sections[at - 1].name)) at -= 1;
136
+ if (at - 1 > 0 && CLOSING.test(sections[at - 1].name)) at -= 1;
137
+ sections.splice(at, 0, _extends({}, ADDED.reviews));
138
+ }
139
+ return sections.length === screen.sections.length ? screen : _extends({}, screen, {
140
+ sections
141
+ });
142
+ }
143
+ // ── Icons for a store's reasons to buy ────────────────────────────────────
144
+ /** Words that suggest an icon from the platform's icon library, first match wins. */ const ICON_WORDS = [
145
+ [
146
+ /\b(ship|shipping|shipped|deliver\w*|dispatch\w*|post(?:age|ed)?|courier)\b/i,
147
+ 'truck'
148
+ ],
149
+ [
150
+ /\b(returns?|refunds?|exchanges?)\b/i,
151
+ 'recycle'
152
+ ],
153
+ [
154
+ /\b(gifts?|gifting|wrapped|wrapping|boxed)\b/i,
155
+ 'gift'
156
+ ],
157
+ [
158
+ /\b(natural|soy|organic|plant|vegan|botanical|clean|non-toxic|eco|sustainab\w*|cotton|wood)\b/i,
159
+ 'leaf'
160
+ ],
161
+ [
162
+ /\b(hand|handmade|hand-poured|poured|small[- ]batch|batch|crafted|made|maker|artisan)\b/i,
163
+ 'care'
164
+ ],
165
+ [
166
+ /\b(guarantee\w*|secure|safe|safety|warranty|protected)\b/i,
167
+ 'shield'
168
+ ],
169
+ [
170
+ /\b(fast|quick|same[- ]day|next[- ]day)\b/i,
171
+ 'fast'
172
+ ],
173
+ [
174
+ /\b(quality|award\w*|premium|finest)\b/i,
175
+ 'award'
176
+ ],
177
+ [
178
+ /\b(scents?|fragrances?|aroma\w*|candles?|glow|light|burn\w*)\b/i,
179
+ 'sparkle'
180
+ ],
181
+ [
182
+ /\b(local|town|city|community)\b/i,
183
+ 'location'
184
+ ],
185
+ [
186
+ /\b(support|help|questions?|chat)\b/i,
187
+ 'chat'
188
+ ]
189
+ ];
190
+ /** The fallbacks an item takes in turn, so a row of icons never repeats one. */ const ICON_FALLBACKS = [
191
+ 'star',
192
+ 'heart',
193
+ 'sparkle',
194
+ 'check',
195
+ 'smile',
196
+ 'award'
197
+ ];
198
+ /**
199
+ * The icon each of a store's reasons to buy shows (AGL-3676): its own where
200
+ * the design named one, else one its words suggest, else the next of a few
201
+ * neutral ones — never the same twice in one row.
202
+ */ export function aiStorefrontItemIcons(items, known) {
203
+ const used = new Set();
204
+ const designed = items.map((item)=>{
205
+ if (item.icon && known(item.icon) && !used.has(item.icon)) {
206
+ used.add(item.icon);
207
+ return item.icon;
208
+ }
209
+ return '';
210
+ });
211
+ return designed.map((icon, index)=>{
212
+ var _ref;
213
+ var _ICON_WORDS_find;
214
+ if (icon) return icon;
215
+ const words = `${items[index].title} ${items[index].text}`;
216
+ const suggested = (_ICON_WORDS_find = ICON_WORDS.find(([pattern, word])=>pattern.test(words) && !used.has(word) && known(word))) == null ? void 0 : _ICON_WORDS_find[1];
217
+ const picked = (_ref = suggested != null ? suggested : ICON_FALLBACKS.find((word)=>!used.has(word) && known(word))) != null ? _ref : 'star';
218
+ used.add(picked);
219
+ return picked;
220
+ });
221
+ }
222
+
223
+ //# sourceMappingURL=ai-layout-storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-storefront.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 * A STORE'S STOREFRONT (AGL-3676, second round).\n *\n * Zach, 2026-10-09, on the beta.237 Willow Wick Candles start (job\n * MtlXWjvt9P): \"again this … site does not look like a store front\". Its home\n * was a lifestyle hero, ONE row of featured products and four text-only bands\n * (\"Why soy, why small batch\", \"How our candles are made\", \"Made to be\n * loved\", \"Gifting and shipping\"); its Shop page opened with a tall hero and\n * the grid below the fold.\n *\n * A designer storefront (Shopify's Dawn, Craft, Sense; Squarespace's commerce\n * templates) is composed of a handful of PARTS, in a familiar order: a hero\n * that sells, the store's bestsellers, its collections as picture tiles, the\n * brand's story beside a photo, why to buy here with icons, what customers\n * say, and a newsletter sign-up. This module names those parts by the words a\n * plan gives its sections, so the plan step can make sure a store's home has\n * them and the compiler and the listings can draw each one its way.\n *\n * It is NOT a section library (Zach 10/7): the model still names, orders and\n * writes every section, and designs each in the layout language; what code\n * adds is a part the plan left out — named in plain words for the model to\n * design — and the platform elements that show the store's own records.\n *\n * Pure: names and plans only.\n */\n\nimport type { AiBuildPlanScreen, AiBuildPlanSection } from '../model/ai-build-plan'\n\n/** A part a storefront home is composed of. */\nexport type AiStorefrontPart = 'featured' | 'collections' | 'story' | 'values' | 'reviews' | 'signup'\n\n/** The section names that say each part, as a plan or a page heading words it. */\nexport const AI_STOREFRONT_PART_WORDS: Readonly<Record<AiStorefrontPart, RegExp>> = {\n featured:\n /\\b(bestsellers?|best[- ]sellers?|best[- ]selling|featured|new arrivals?|arrivals|just in|favou?rites?|signature|most loved|top picks?|our (?:candles|products|picks)|shop (?:all|now|the))\\b/i,\n collections: /\\b(collections?|categor(?:y|ies)|shop by|browse by|by scent|scents?|ranges?|families|occasions?|edits?)\\b/i,\n story: /\\b(story|about|our craft|craft|making|made|maker|process|how (?:we|it)|behind|studio|workshop)\\b/i,\n values: /\\b(why|promise|promises|values?|difference|shipping|returns?|guarantee|perks?|benefits?|care|small[- ]batch|ingredients?|sustainab\\w*)\\b/i,\n reviews: /\\b(reviews?|testimonials?|customers? (?:say|love)|what (?:people|customers|our customers) say|kind words|praise|loved by|social proof)\\b/i,\n signup: /\\b(newsletter|subscribe|sign[- ]?up|mailing list|join (?:us|the list|our list)|stay in touch|in the loop|first to know|inbox|letters? from)\\b/i,\n}\n\n/** Whether a planned section's name says a part. */\nexport function aiStorefrontSays(part: AiStorefrontPart, name: string): boolean {\n return AI_STOREFRONT_PART_WORDS[part].test(name)\n}\n\n/** The plan entry a part the plan left out is added as: plain words the page step designs. */\nconst ADDED: Readonly<Record<'featured' | 'collections' | 'reviews' | 'signup', AiBuildPlanSection>> = {\n featured: { name: 'Bestsellers', uses: [], items: 4 },\n collections: { name: 'Shop by collection', uses: [], items: 3 },\n reviews: { name: 'Customer reviews', uses: [], items: 0 },\n signup: { name: 'Newsletter sign-up', uses: [], items: 0 },\n}\n\n/** The most sections one page of a site start holds (`AI_SITE_MAX_SECTIONS`). */\nconst MOST_SECTIONS = 8\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\n\n/** A section that closes a page: its call to action, a contact or a sign-up band. */\nconst CLOSING = /\\b(cta|call to action|closing|close|contact|get in touch|visit)\\b/i\n\n/**\n * A store plan's screens with the storefront a shopper expects (AGL-3676):\n *\n * - THE HOME (paid starts only — the Free wall prices each section, and a\n * Free store sells nothing yet): its hero first, then any part the plan\n * left out, added under plain names while the page has room (eight\n * sections), in order of need: the bestsellers the platform lists,\n * right after the hero; the collections as picture tiles after them; the\n * newsletter sign-up as the page's last band; and customer reviews (the\n * store's real ones, which show only once shoppers leave them) before\n * the close.\n * - THE SHOP PAGE: grid first. Its product grid section moves to second,\n * straight under the page's short title, so the products open above the\n * fold; anything the plan put between them (an intro, a story) follows\n * the grid.\n *\n * Every section the plan named stays, in its order otherwise; nothing is\n * removed, and a page at eight sections is given nothing more.\n */\nexport function aiStorefrontPlanScreens<S extends Pick<AiBuildPlanScreen, 'slug' | 'title' | 'sections'>>(\n screens: readonly S[],\n options: { paid: boolean; isShopPage: (screen: S) => boolean; gridSection: (sections: S['sections']) => number },\n): S[] {\n return screens.map((screen) => {\n if (firstSegment(screen.slug) === '') return options.paid ? storefrontHome(screen) : screen\n if (!options.isShopPage(screen) || screen.sections.length < 3) return screen\n const grid = options.gridSection(screen.sections)\n if (grid <= 1) return screen\n const sections = [...screen.sections]\n const [moved] = sections.splice(grid, 1)\n sections.splice(1, 0, moved)\n return { ...screen, sections }\n })\n}\n\nfunction storefrontHome<S extends Pick<AiBuildPlanScreen, 'sections'>>(screen: S): S {\n const sections = [...screen.sections]\n if (!sections.length) return screen\n // A section about the collections that also names the bestsellers is the bestsellers'.\n const says = (part: AiStorefrontPart, name: string) =>\n aiStorefrontSays(part, name) && (part !== 'collections' || !aiStorefrontSays('featured', name))\n const has = (part: AiStorefrontPart) => sections.some((section, index) => index > 0 && says(part, section.name))\n const room = () => sections.length < MOST_SECTIONS\n // What a shopper meets first matters most, so the parts are added in that\n // order while the page has room: the bestsellers, the collections, the\n // sign-up, then the reviews — which show nothing until a shopper writes one.\n if (!has('featured') && room()) sections.splice(1, 0, { ...ADDED.featured })\n if (!has('collections') && room()) {\n // The collections follow the bestsellers.\n const featured = sections.findIndex((section, index) => index > 0 && says('featured', section.name))\n sections.splice(featured > 0 ? featured + 1 : 1, 0, { ...ADDED.collections })\n }\n // The newsletter is the page's last band, under any closing call to action.\n if (!has('signup') && room()) sections.push({ ...ADDED.signup })\n if (!has('reviews') && room()) {\n // The reviews come just before the page closes: above a closing sign-up\n // and above a closing call to action.\n let at = sections.length\n if (at - 1 > 0 && says('signup', sections[at - 1].name)) at -= 1\n if (at - 1 > 0 && CLOSING.test(sections[at - 1].name)) at -= 1\n sections.splice(at, 0, { ...ADDED.reviews })\n }\n return sections.length === screen.sections.length ? screen : { ...screen, sections }\n}\n\n// ── Icons for a store's reasons to buy ────────────────────────────────────\n\n/** Words that suggest an icon from the platform's icon library, first match wins. */\nconst ICON_WORDS: ReadonlyArray<readonly [RegExp, string]> = [\n [/\\b(ship|shipping|shipped|deliver\\w*|dispatch\\w*|post(?:age|ed)?|courier)\\b/i, 'truck'],\n [/\\b(returns?|refunds?|exchanges?)\\b/i, 'recycle'],\n [/\\b(gifts?|gifting|wrapped|wrapping|boxed)\\b/i, 'gift'],\n [/\\b(natural|soy|organic|plant|vegan|botanical|clean|non-toxic|eco|sustainab\\w*|cotton|wood)\\b/i, 'leaf'],\n [/\\b(hand|handmade|hand-poured|poured|small[- ]batch|batch|crafted|made|maker|artisan)\\b/i, 'care'],\n [/\\b(guarantee\\w*|secure|safe|safety|warranty|protected)\\b/i, 'shield'],\n [/\\b(fast|quick|same[- ]day|next[- ]day)\\b/i, 'fast'],\n [/\\b(quality|award\\w*|premium|finest)\\b/i, 'award'],\n [/\\b(scents?|fragrances?|aroma\\w*|candles?|glow|light|burn\\w*)\\b/i, 'sparkle'],\n [/\\b(local|town|city|community)\\b/i, 'location'],\n [/\\b(support|help|questions?|chat)\\b/i, 'chat'],\n]\n\n/** The fallbacks an item takes in turn, so a row of icons never repeats one. */\nconst ICON_FALLBACKS = ['star', 'heart', 'sparkle', 'check', 'smile', 'award'] as const\n\n/**\n * The icon each of a store's reasons to buy shows (AGL-3676): its own where\n * the design named one, else one its words suggest, else the next of a few\n * neutral ones — never the same twice in one row.\n */\nexport function aiStorefrontItemIcons(\n items: ReadonlyArray<{ title: string; text: string; icon?: string }>,\n known: (word: string | undefined) => boolean,\n): string[] {\n const used = new Set<string>()\n const designed = items.map((item) => {\n if (item.icon && known(item.icon) && !used.has(item.icon)) {\n used.add(item.icon)\n return item.icon\n }\n return ''\n })\n return designed.map((icon, index) => {\n if (icon) return icon\n const words = `${items[index].title} ${items[index].text}`\n const suggested = ICON_WORDS.find(([pattern, word]) => pattern.test(words) && !used.has(word) && known(word))?.[1]\n const picked = suggested ?? ICON_FALLBACKS.find((word) => !used.has(word) && known(word)) ?? 'star'\n used.add(picked)\n return picked\n })\n}\n"],"names":["AI_STOREFRONT_PART_WORDS","featured","collections","story","values","reviews","signup","aiStorefrontSays","part","name","test","ADDED","uses","items","MOST_SECTIONS","firstSegment","slug","trim","replace","split","toLowerCase","CLOSING","aiStorefrontPlanScreens","screens","options","map","screen","paid","storefrontHome","isShopPage","sections","length","grid","gridSection","moved","splice","says","has","some","section","index","room","findIndex","push","at","ICON_WORDS","ICON_FALLBACKS","aiStorefrontItemIcons","known","used","Set","designed","item","icon","add","suggested","words","title","text","find","pattern","word","picked"],"mappings":"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"}
@@ -44,6 +44,14 @@ export declare const AI_BUILD_LIMITS: {
44
44
  /** A Free workspace's pages. */
45
45
  readonly freePages: 2;
46
46
  };
47
+ /**
48
+ * What a Free build's plan card says when it holds as many pages as a Free
49
+ * build makes (AGL-3722): the plan is told the cap and plans the first pages
50
+ * a request asks for, so a request for three — "About us, Custom cakes and
51
+ * Catering", 2026-10-10 — came back as two with nothing saying why. `null`
52
+ * when the plan holds fewer, or on a paid workspace.
53
+ */
54
+ export declare function aiBuildFreePageCapNote(plan: Pick<AiBuildPlan, 'screens'>, freeTaste: boolean): string | null;
47
55
  /** What a build creates through its `create` list; everything else beyond pages is an item. */
48
56
  export declare const AI_BUILD_CREATE_KINDS: readonly AiBuildPlanCreateKind[];
49
57
  /**
@@ -127,7 +135,7 @@ export declare function aiBuildCreditEstimate(plan: AiBuildPlan, options?: {
127
135
  export declare function aiJobCreditEstimate(kind: string, plan: AiBuildPlan): number;
128
136
  /**
129
137
  * What one unit is likely to cost, its p90 and its ceiling (AGL-3722): a page
130
- * by its sections around the measured page, a creation at what its kind
138
+ * as a page written on its own measured (a fixed cost and one per section), a creation at what its kind
131
139
  * measured, an item at the measured pass times the passes its capability
132
140
  * estimates (none for a draft another plugin writes, which asks no model).
133
141
  * The ceiling is `aiBuildUnitCreditEstimate`, unchanged.
@@ -195,6 +203,8 @@ export declare function aiBuildDegradation(unit: AiBuildUnit, context: {
195
203
  ledger: readonly AiJobItemLedger[];
196
204
  ops?: AiBuildOps;
197
205
  }): AiBuildDegradation;
206
+ /** The dataset a form unit writes to, by its `new:` name lowercased; `null` for any other unit (AGL-3616). */
207
+ export declare function aiBuildUnitWritesTo(unit: Pick<AiBuildUnit, 'creation'>): string | null;
198
208
  /** What each creation is called in a note. */
199
209
  export declare function aiBuildCreationNoun(kind: AiBuildPlanCreateKind): string;
200
210
  /**
@@ -16,7 +16,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
16
16
  * limitations under the License.
17
17
  */ import { pluginAiCapabilityArgsProblems } from "@aglyn/aglyn/plugin-manager/plugin-ai-capabilities";
18
18
  import { AI_PLAN_NEW_REF_PREFIX, isAiPlanNewRef } from "./ai-build-plan.js";
19
- import { AI_SITE_HOME_FIRST_LABEL, AI_SITE_MAX_SECTIONS, AI_SITE_PASS_CREDITS, aiCreationMeasuredCredits, aiJobPlanCreditEstimate, aiJobPlanCreditRange, aiScreenCreditRange, aiSitePlanIsHome } from "./ai-site-job.js";
19
+ import { AI_SITE_HOME_FIRST_LABEL, AI_SITE_MAX_SECTIONS, AI_SITE_PASS_CREDITS, aiCreationMeasuredCredits, aiJobPlanCreditEstimate, aiJobPlanCreditRange, aiSitePlanIsHome, aiStandaloneScreenCreditRange } from "./ai-site-job.js";
20
20
  import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCreditRangeOf, aiCreditRangeOrdered } from "./ai-credit-estimate.js";
21
21
  /**
22
22
  * What a `build` job is (AGL-3616): one request — "a few pages, a contact
@@ -40,6 +40,17 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCre
40
40
  pages: 8,
41
41
  /** A Free workspace's pages. */ freePages: 2
42
42
  };
43
+ /**
44
+ * What a Free build's plan card says when it holds as many pages as a Free
45
+ * build makes (AGL-3722): the plan is told the cap and plans the first pages
46
+ * a request asks for, so a request for three — "About us, Custom cakes and
47
+ * Catering", 2026-10-10 — came back as two with nothing saying why. `null`
48
+ * when the plan holds fewer, or on a paid workspace.
49
+ */ export function aiBuildFreePageCapNote(plan, freeTaste) {
50
+ const cap = AI_BUILD_LIMITS.freePages;
51
+ if (!freeTaste || plan.screens.length < cap) return null;
52
+ return `A Free workspace’s build makes up to ${cap} pages at a time. If you asked for more, ask Assist for the rest once this build is done.`;
53
+ }
43
54
  /** What a build creates through its `create` list; everything else beyond pages is an item. */ export const AI_BUILD_CREATE_KINDS = [
44
55
  'layout',
45
56
  'form',
@@ -96,15 +107,18 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCre
96
107
  };
97
108
  const units = [];
98
109
  plan.create.forEach((creation, index)=>{
110
+ var _creation_writesTo;
99
111
  if (!AI_BUILD_CREATE_KINDS.includes(creation.kind)) return;
100
112
  const slot = `c${index}`;
113
+ // A form is built after the dataset it writes to (AGL-3616).
101
114
  units.push({
102
115
  slot,
103
116
  op: creationOp(creation),
104
117
  label: creation.name,
105
118
  creation,
106
119
  deps: depsOf([
107
- creation.duplicateOf
120
+ creation.duplicateOf,
121
+ creation.kind === 'form' ? (_creation_writesTo = creation.writesTo) != null ? _creation_writesTo : null : null
108
122
  ], slot)
109
123
  });
110
124
  });
@@ -266,14 +280,17 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCre
266
280
  }
267
281
  /**
268
282
  * What one unit is likely to cost, its p90 and its ceiling (AGL-3722): a page
269
- * by its sections around the measured page, a creation at what its kind
283
+ * as a page written on its own measured (a fixed cost and one per section), a creation at what its kind
270
284
  * measured, an item at the measured pass times the passes its capability
271
285
  * estimates (none for a draft another plugin writes, which asks no model).
272
286
  * The ceiling is `aiBuildUnitCreditEstimate`, unchanged.
273
287
  */ export function aiBuildUnitCreditRange(unit, ops) {
274
288
  const ceiling = aiBuildUnitCreditEstimate(unit, ops);
275
- if (unit.screen) return aiScreenCreditRange(unit.screen.sections.length);
276
- if (unit.creation) return aiCreditRangeOf(aiCreationMeasuredCredits(unit.creation.kind), ceiling);
289
+ // A build writes each page on its own, never in a site start's warm run (AGL-3722).
290
+ if (unit.screen) return aiStandaloneScreenCreditRange(unit.screen.sections.length);
291
+ if (unit.creation) return aiCreditRangeOf(aiCreationMeasuredCredits(unit.creation.kind, {
292
+ standalone: true
293
+ }), ceiling);
277
294
  const passes = Math.ceil(ceiling / AI_SITE_PASS_CREDITS);
278
295
  return aiCreditRangeOf({
279
296
  median: passes * AI_MEASURED_PASS_CREDITS.median,
@@ -341,7 +358,7 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCre
341
358
  const smaller = aiBuildFirstPagePlan(plan);
342
359
  if (!smaller) return null;
343
360
  const screen = smaller.screens[0];
344
- const label = aiSitePlanIsHome(screen) ? AI_SITE_HOME_FIRST_LABEL : `Build the page “${screen.title}” first`;
361
+ const label = aiSitePlanIsHome(screen) ? AI_SITE_HOME_FIRST_LABEL : `Build the ${screen.title} page first`;
345
362
  return _extends({
346
363
  label
347
364
  }, aiBuildCreditRange(smaller, {
@@ -421,6 +438,13 @@ const OPEN = [
421
438
  const noun = dep.creation ? aiBuildCreationNoun(dep.creation.kind) : (_ref = (_context_ops = context.ops) == null ? void 0 : (_context_ops_get = _context_ops.get(dep.op)) == null ? void 0 : _context_ops_get.noun) != null ? _ref : dep.op;
422
439
  const capability = dep.item ? (_context_ops1 = context.ops) == null ? void 0 : _context_ops1.get(dep.op) : undefined;
423
440
  const degrade = (_ref1 = (_ref2 = (_dep_item = dep.item) == null ? void 0 : _dep_item.degrade) != null ? _ref2 : capability == null ? void 0 : capability.degrade) != null ? _ref1 : ((_dep_creation = dep.creation) == null ? void 0 : _dep_creation.kind) === 'layout' ? 'fallback' : 'omit';
441
+ // A form whose dataset could not be made is built without it (AGL-3616):
442
+ // its submissions reach the Inbox, as any form's do.
443
+ if (aiBuildUnitWritesTo(unit) === dep.label.toLowerCase()) {
444
+ result.degradedBy.push(slot);
445
+ result.notes.push(`Its submissions arrive in the Inbox only: the dataset “${dep.label}” could not be created.`);
446
+ continue;
447
+ }
424
448
  if (!unit.screen && degrade === 'omit') {
425
449
  result.skip = `Not built: the ${noun} “${dep.label}” it needs could not be created.`;
426
450
  result.degradedBy.push(slot);
@@ -444,6 +468,11 @@ const OPEN = [
444
468
  }
445
469
  return result;
446
470
  }
471
+ /** The dataset a form unit writes to, by its `new:` name lowercased; `null` for any other unit (AGL-3616). */ export function aiBuildUnitWritesTo(unit) {
472
+ var _unit_creation;
473
+ const ref = ((_unit_creation = unit.creation) == null ? void 0 : _unit_creation.kind) === 'form' ? unit.creation.writesTo : null;
474
+ return isAiPlanNewRef(ref) ? aiBuildRefName(ref) : null;
475
+ }
447
476
  /** What each creation is called in a note. */ export function aiBuildCreationNoun(kind) {
448
477
  return kind === 'email' ? 'email design' : kind === 'theme-change' ? 'theme change' : kind;
449
478
  }