@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
@@ -0,0 +1,41 @@
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
+ /** What is still hollow in a frame: the node ids, by kind. */
18
+ export interface AiLayoutFrameHollows {
19
+ /** Footer grid columns holding nothing, or nothing but headings. */
20
+ footerColumns: string[];
21
+ /** Footer headings with nothing under them. */
22
+ footerHeadings: string[];
23
+ /** Footer rows whose columns are centered against each other rather than top-aligned. */
24
+ footerCenteredRows: string[];
25
+ /** Header Dropdown Menus holding no link. */
26
+ headerMenus: string[];
27
+ }
28
+ /** What is still hollow in a frame (AGL-3735); every list empty when nothing is. */
29
+ export declare function aiLayoutFrameHollows(nodes: Readonly<Record<string, unknown>>): AiLayoutFrameHollows;
30
+ /** Whether a frame has anything hollow in it. */
31
+ export declare function aiLayoutFrameIsHollow(hollows: AiLayoutFrameHollows): boolean;
32
+ /** The sentences a check reports for what is hollow. */
33
+ export declare function aiLayoutFrameHollowsSaid(hollows: AiLayoutFrameHollows): string[];
34
+ /**
35
+ * Takes what is hollow out of a frame, IN PLACE (AGL-3735): see the file's
36
+ * comment. Returns whether anything changed. A frame left holding nothing is
37
+ * taken out after it, up to the row, the footer's or the header's own element.
38
+ */
39
+ export declare function aiLayoutRepairFrameHollows(nodes: Record<string, unknown>): boolean;
40
+ /** A frame with what is hollow taken out (AGL-3735), as a copy; `null` when nothing was. */
41
+ export declare function aiLayoutWithoutFrameHollows<T extends Readonly<Record<string, unknown>>>(nodes: T): T | null;
@@ -0,0 +1,330 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { AI_LAYOUT_FRAMES, aiLayoutSpans } from "./ai-layout-tree.js";
18
+ /** A Typography variant that heads what follows it, never content of its own. */ const HEADING_VARIANTS = new Set([
19
+ 'h1',
20
+ 'h2',
21
+ 'h3',
22
+ 'h4',
23
+ 'h5',
24
+ 'h6',
25
+ 'subtitle1',
26
+ 'subtitle2',
27
+ 'overline'
28
+ ]);
29
+ /** The Besigner's Dropdown Menu and Mega Menu. */ const DROPDOWNS = new Set([
30
+ 'muiNavMenu',
31
+ 'muiMegaMenu'
32
+ ]);
33
+ const LINK = 'muiScreenLink';
34
+ /** Frames that hold content without being it: the language's, and a plain element. */ const HOLDERS = new Set([
35
+ ...AI_LAYOUT_FRAMES,
36
+ 'div',
37
+ 'span',
38
+ 'muiGrid'
39
+ ]);
40
+ const renders = (node, element)=>{
41
+ var _node_props, _node_props1;
42
+ return ((_node_props = node.props) == null ? void 0 : _node_props['element']) === element || ((_node_props1 = node.props) == null ? void 0 : _node_props1['component']) === element || node.componentId === element;
43
+ };
44
+ const isHeading = (node)=>{
45
+ var _ref;
46
+ var _node_props;
47
+ return !!node && node.componentId === 'muiTypography' && HEADING_VARIANTS.has(String((_ref = (_node_props = node.props) == null ? void 0 : _node_props['variant']) != null ? _ref : ''));
48
+ };
49
+ function inspect(map) {
50
+ const subtree = (id)=>{
51
+ const out = [];
52
+ const visit = (at)=>{
53
+ var _map_at_nodes;
54
+ if (!map[at] || out.includes(at)) return;
55
+ out.push(at);
56
+ for (const child of (_map_at_nodes = map[at].nodes) != null ? _map_at_nodes : [])visit(child);
57
+ };
58
+ visit(id);
59
+ return out;
60
+ };
61
+ /** The leaves under a node, in document order: what a visitor reads. */ const leaves = (id)=>subtree(id).filter((at)=>{
62
+ var _node_nodes;
63
+ const node = map[at];
64
+ return !((_node_nodes = node.nodes) != null ? _node_nodes : []).some((child)=>map[child]) && !HOLDERS.has(String(node.componentId));
65
+ });
66
+ const footers = Object.keys(map).filter((id)=>renders(map[id], 'footer'));
67
+ const headers = Object.keys(map).filter((id)=>map[id].componentId === 'muiAppBar' || renders(map[id], 'header'));
68
+ const footerIds = new Set(footers.flatMap(subtree));
69
+ const rows = [
70
+ ...footerIds
71
+ ].filter((id)=>{
72
+ var _map_id_props;
73
+ return map[id].componentId === 'muiGrid' && ((_map_id_props = map[id].props) == null ? void 0 : _map_id_props['container']);
74
+ });
75
+ const columns = rows.flatMap((row)=>{
76
+ var _map_row_nodes;
77
+ return ((_map_row_nodes = map[row].nodes) != null ? _map_row_nodes : []).filter((id)=>{
78
+ var _map_id;
79
+ return ((_map_id = map[id]) == null ? void 0 : _map_id.componentId) === 'muiGrid';
80
+ });
81
+ });
82
+ const footerColumns = columns.filter((id)=>leaves(id).every((leaf)=>isHeading(map[leaf])));
83
+ // The Tidewater shape: a column of only a heading, then a column of only
84
+ // links with no heading. The links are what the heading heads.
85
+ const merges = [];
86
+ for (const row of rows){
87
+ var _map_row_nodes;
88
+ const inRow = ((_map_row_nodes = map[row].nodes) != null ? _map_row_nodes : []).filter((id)=>{
89
+ var _map_id;
90
+ return ((_map_id = map[id]) == null ? void 0 : _map_id.componentId) === 'muiGrid';
91
+ });
92
+ inRow.forEach((id, at)=>{
93
+ const next = inRow[at + 1];
94
+ if (!next || !footerColumns.includes(id) || !leaves(id).length) return;
95
+ const read = leaves(next);
96
+ if (read.length && read.every((leaf)=>map[leaf].componentId === LINK)) merges.push({
97
+ heading: id,
98
+ links: next
99
+ });
100
+ });
101
+ }
102
+ // A heading is hollow when nothing but headings follow it before the next
103
+ // heading of its own (an overline labels the heading after it, so it
104
+ // reaches one heading further).
105
+ const footerHeadings = [];
106
+ const flows = columns.length ? columns : footers;
107
+ for (const flow of flows){
108
+ if (footerColumns.includes(flow)) continue;
109
+ const read = leaves(flow);
110
+ read.forEach((id, at)=>{
111
+ var _map_id_props;
112
+ if (!isHeading(map[id])) return;
113
+ let headingsLeft = ((_map_id_props = map[id].props) == null ? void 0 : _map_id_props['variant']) === 'overline' ? 2 : 1;
114
+ for (const next of read.slice(at + 1)){
115
+ var _map_next_props;
116
+ if (!isHeading(map[next])) return;
117
+ if (((_map_next_props = map[next].props) == null ? void 0 : _map_next_props['variant']) !== 'overline') headingsLeft -= 1;
118
+ if (headingsLeft <= 0) break;
119
+ }
120
+ footerHeadings.push(id);
121
+ });
122
+ }
123
+ const footerCenteredRows = rows.filter((id)=>{
124
+ var _map_id_sx;
125
+ return ((_map_id_sx = map[id].sx) == null ? void 0 : _map_id_sx['alignItems']) === 'center';
126
+ });
127
+ const headerIds = new Set(headers.flatMap(subtree));
128
+ const headerMenus = [
129
+ ...headerIds
130
+ ].filter((id)=>DROPDOWNS.has(String(map[id].componentId)) && !subtree(id).some((at)=>at !== id && map[at].componentId === LINK));
131
+ return {
132
+ footerColumns,
133
+ footerHeadings,
134
+ footerCenteredRows,
135
+ headerMenus,
136
+ rows,
137
+ merges
138
+ };
139
+ }
140
+ /** What is still hollow in a frame (AGL-3735); every list empty when nothing is. */ export function aiLayoutFrameHollows(nodes) {
141
+ const { footerColumns, footerHeadings, footerCenteredRows, headerMenus } = inspect(nodes);
142
+ return {
143
+ footerColumns,
144
+ footerHeadings,
145
+ footerCenteredRows,
146
+ headerMenus
147
+ };
148
+ }
149
+ /** Whether a frame has anything hollow in it. */ export function aiLayoutFrameIsHollow(hollows) {
150
+ return [
151
+ hollows.footerColumns,
152
+ hollows.footerHeadings,
153
+ hollows.footerCenteredRows,
154
+ hollows.headerMenus
155
+ ].some((ids)=>ids.length > 0);
156
+ }
157
+ /** The sentences a check reports for what is hollow. */ export function aiLayoutFrameHollowsSaid(hollows) {
158
+ return [
159
+ ...hollows.footerColumns.length ? [
160
+ `${hollows.footerColumns.length} footer column(s) hold only a heading or nothing`
161
+ ] : [],
162
+ ...hollows.footerHeadings.length ? [
163
+ `${hollows.footerHeadings.length} footer heading(s) have nothing under them`
164
+ ] : [],
165
+ ...hollows.footerCenteredRows.length ? [
166
+ 'the footer’s columns are centered, not top-aligned'
167
+ ] : [],
168
+ ...hollows.headerMenus.length ? [
169
+ `${hollows.headerMenus.length} header dropdown(s) hold no link`
170
+ ] : []
171
+ ];
172
+ }
173
+ /**
174
+ * Takes what is hollow out of a frame, IN PLACE (AGL-3735): see the file's
175
+ * comment. Returns whether anything changed. A frame left holding nothing is
176
+ * taken out after it, up to the row, the footer's or the header's own element.
177
+ */ export function aiLayoutRepairFrameHollows(nodes) {
178
+ const map = nodes;
179
+ let changed = false;
180
+ const parentOf = (id)=>{
181
+ var _Object_keys_find;
182
+ return (_Object_keys_find = Object.keys(map).find((at)=>{
183
+ var _map_at_nodes;
184
+ return (_map_at_nodes = map[at].nodes) == null ? void 0 : _map_at_nodes.includes(id);
185
+ })) != null ? _Object_keys_find : null;
186
+ };
187
+ const remove = (id)=>{
188
+ const tree = (at)=>{
189
+ var _ref;
190
+ var _map_at;
191
+ return [
192
+ at,
193
+ ...((_ref = (_map_at = map[at]) == null ? void 0 : _map_at.nodes) != null ? _ref : []).flatMap(tree)
194
+ ];
195
+ };
196
+ let parent = parentOf(id);
197
+ for (const at of tree(id))delete map[at];
198
+ // Up the tree: a frame that held only what went is gone with it.
199
+ let child = id;
200
+ while(parent && map[parent]){
201
+ var _node_nodes;
202
+ var _node_props;
203
+ const node = map[parent];
204
+ node.nodes = ((_node_nodes = node.nodes) != null ? _node_nodes : []).filter((at)=>at !== child);
205
+ const empty = !node.nodes.some((at)=>map[at]);
206
+ const stops = renders(node, 'footer') || renders(node, 'header') || node.componentId === 'muiAppBar' || node.componentId === 'muiGrid' && ((_node_props = node.props) == null ? void 0 : _node_props['container']);
207
+ if (!empty || stops || !HOLDERS.has(String(node.componentId))) break;
208
+ child = parent;
209
+ parent = parentOf(parent);
210
+ delete map[child];
211
+ }
212
+ changed = true;
213
+ };
214
+ for(let pass = 0; pass < 4; pass += 1){
215
+ const hollows = inspect(map);
216
+ if (!aiLayoutFrameIsHollow(hollows)) break;
217
+ for (const id of hollows.footerCenteredRows){
218
+ var _map_id_sx;
219
+ map[id].sx = _extends({}, (_map_id_sx = map[id].sx) != null ? _map_id_sx : {}, {
220
+ alignItems: 'flex-start'
221
+ });
222
+ changed = true;
223
+ }
224
+ for (const id of hollows.headerMenus)if (map[id]) remove(id);
225
+ const headed = new Set();
226
+ for (const merge of hollows.merges){
227
+ if (map[merge.heading] && map[merge.links] && headOver(map, merge)) {
228
+ headed.add(merge.heading);
229
+ changed = true;
230
+ }
231
+ }
232
+ for (const id of hollows.footerHeadings)if (map[id]) remove(id);
233
+ for (const id of hollows.footerColumns)if (map[id] && !headed.has(id)) remove(id);
234
+ // Every row that lost a column, however it lost it, reflows.
235
+ for (const row of hollows.rows)if (map[row] && reflow(map, row)) changed = true;
236
+ }
237
+ return changed;
238
+ }
239
+ /**
240
+ * A heading-only column takes in the heading-less column of links after it,
241
+ * as one flow (heading, then links), and the links' column goes; whether it
242
+ * changed. The row it leaves one column short reflows after.
243
+ */ function headOver(map, { heading, links }) {
244
+ var _map_links_nodes, _map_heading_nodes;
245
+ const moved = ((_map_links_nodes = map[links].nodes) != null ? _map_links_nodes : []).filter((id)=>map[id]);
246
+ if (!moved.length) return false;
247
+ const own = ((_map_heading_nodes = map[heading].nodes) != null ? _map_heading_nodes : []).filter((id)=>map[id]);
248
+ const flow = own.length === 1 && map[own[0]].componentId === 'muiStack' ? own[0] : null;
249
+ if (flow) {
250
+ var _map_flow_nodes;
251
+ map[flow].nodes = [
252
+ ...(_map_flow_nodes = map[flow].nodes) != null ? _map_flow_nodes : [],
253
+ ...moved
254
+ ];
255
+ } else {
256
+ let id = `${heading}-flow`;
257
+ for(let n = 2; map[id]; n += 1)id = `${heading}-flow${n}`;
258
+ // The compiler's flow of a column (`compileFlow`): a stack, 2.5 apart.
259
+ map[id] = {
260
+ componentId: 'muiStack',
261
+ props: {
262
+ spacing: '2.5'
263
+ },
264
+ nodes: [
265
+ ...own,
266
+ ...moved
267
+ ]
268
+ };
269
+ map[heading].nodes = [
270
+ id
271
+ ];
272
+ }
273
+ delete map[links];
274
+ for (const node of Object.values(map)){
275
+ var _node_nodes;
276
+ if ((_node_nodes = node.nodes) == null ? void 0 : _node_nodes.includes(links)) node.nodes = node.nodes.filter((at)=>at !== links);
277
+ }
278
+ return true;
279
+ }
280
+ /** The columns a row has left, spread across its twelve as their widths were; whether it changed. */ function reflow(map, row) {
281
+ var _map_row_nodes, _ref;
282
+ var _map_columns__props, _map_row_props, _map_row_props1;
283
+ const columns = ((_map_row_nodes = map[row].nodes) != null ? _map_row_nodes : []).filter((id)=>{
284
+ var _map_id;
285
+ return ((_map_id = map[id]) == null ? void 0 : _map_id.componentId) === 'muiGrid';
286
+ });
287
+ const md = columns.map((id)=>{
288
+ var _ref;
289
+ var _exec, _map_id_props;
290
+ return (_exec = /(?:^|\s)md:(\d+)/.exec(String((_ref = (_map_id_props = map[id].props) == null ? void 0 : _map_id_props['size']) != null ? _ref : ''))) == null ? void 0 : _exec[1];
291
+ });
292
+ if (!columns.length || md.some((span)=>!span)) return false;
293
+ // A row whose columns fill it, as many as it was drawn for, is as it was drawn.
294
+ const drawnFor = /sm:6/.test(String((_ref = (_map_columns__props = map[columns[0]].props) == null ? void 0 : _map_columns__props['size']) != null ? _ref : '')) ? 4 : 3;
295
+ if (md.reduce((sum, span)=>sum + Number(span), 0) === 12 && columns.length >= 4 === (drawnFor === 4)) return false;
296
+ const spans = columns.length === 1 ? [
297
+ 12
298
+ ] : aiLayoutSpans(md.map(Number));
299
+ columns.forEach((id, at)=>{
300
+ var _map_id_props;
301
+ map[id].props = _extends({}, (_map_id_props = map[id].props) != null ? _map_id_props : {}, {
302
+ size: `${columns.length >= 4 ? 'xs:12 sm:6' : 'xs:12'} md:${spans[at]}`
303
+ });
304
+ });
305
+ // The compiler's row spacing for that many columns (`compileRow`).
306
+ if (((_map_row_props = map[row].props) == null ? void 0 : _map_row_props['spacing']) === '4' || ((_map_row_props1 = map[row].props) == null ? void 0 : _map_row_props1['spacing']) === '6') {
307
+ map[row].props = _extends({}, map[row].props, {
308
+ spacing: columns.length >= 3 ? '4' : '6'
309
+ });
310
+ }
311
+ return true;
312
+ }
313
+ /** A frame with what is hollow taken out (AGL-3735), as a copy; `null` when nothing was. */ export function aiLayoutWithoutFrameHollows(nodes) {
314
+ const copy = {};
315
+ for (const [id, raw] of Object.entries(nodes)){
316
+ const node = raw;
317
+ copy[id] = node && typeof node === 'object' ? _extends({}, node, node.props ? {
318
+ props: _extends({}, node.props)
319
+ } : {}, node.sx ? {
320
+ sx: _extends({}, node.sx)
321
+ } : {}, node.nodes ? {
322
+ nodes: [
323
+ ...node.nodes
324
+ ]
325
+ } : {}) : node;
326
+ }
327
+ return aiLayoutRepairFrameHollows(copy) ? copy : null;
328
+ }
329
+
330
+ //# sourceMappingURL=ai-layout-frame-hollows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame-hollows.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_LAYOUT_FRAMES, aiLayoutSpans } from './ai-layout-tree'\n\n/**\n * A FRAME WITH NOTHING HOLLOW IN IT (AGL-3735).\n *\n * The live Tidewater Soap Works footer (job S0vnUzhGar, 10/11) had four\n * columns: the brand, \"Shop\" over the site's pages, \"Our shop\" over NOTHING,\n * and the store's account and policy links under no heading — the list the\n * design put under \"Our shop\" was left out, its heading and column stayed,\n * and the grid centered the columns so each sat at its own height.\n *\n * A heading with nothing under it, and a column with nothing but headings,\n * is never published. So, wherever the frame's links change — the compiler,\n * a publish taking out links to pages that were never written, a menu change\n * taking a link out — and once more on what a publish writes:\n *\n * - in the footer, a column of only a heading followed by a column of only\n * links with no heading (the Tidewater shape) becomes one column: the\n * heading over those links;\n * - then a heading with no content under it is taken out, then a column\n * holding nothing (or nothing but headings) is, and the columns left\n * reflow across the row's twelve;\n * - the footer's rows are top-aligned;\n * - in the header, a Dropdown Menu left holding no link is taken out.\n *\n * `aiLayoutFrameHollows` is the check: what is still hollow.\n *\n * Pure over a node map (`{ id: { componentId, props, sx, nodes } }`) — the\n * compiler's, a stored version's, or a publish's working copy.\n */\n\ninterface HollowNode {\n componentId?: string\n props?: Record<string, unknown>\n sx?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\ntype NodeMap = Record<string, HollowNode>\n\n/** A Typography variant that heads what follows it, never content of its own. */\nconst HEADING_VARIANTS: ReadonlySet<string> = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'subtitle1', 'subtitle2', 'overline'])\n/** The Besigner's Dropdown Menu and Mega Menu. */\nconst DROPDOWNS: ReadonlySet<string> = new Set(['muiNavMenu', 'muiMegaMenu'])\nconst LINK = 'muiScreenLink'\n/** Frames that hold content without being it: the language's, and a plain element. */\nconst HOLDERS: ReadonlySet<string> = new Set([...AI_LAYOUT_FRAMES, 'div', 'span', 'muiGrid'])\n\nconst renders = (node: HollowNode, element: string) =>\n node.props?.['element'] === element || node.props?.['component'] === element || node.componentId === element\n\nconst isHeading = (node: HollowNode | undefined) =>\n !!node && node.componentId === 'muiTypography' && HEADING_VARIANTS.has(String(node.props?.['variant'] ?? ''))\n\n/** What is still hollow in a frame: the node ids, by kind. */\nexport interface AiLayoutFrameHollows {\n /** Footer grid columns holding nothing, or nothing but headings. */\n footerColumns: string[]\n /** Footer headings with nothing under them. */\n footerHeadings: string[]\n /** Footer rows whose columns are centered against each other rather than top-aligned. */\n footerCenteredRows: string[]\n /** Header Dropdown Menus holding no link. */\n headerMenus: string[]\n}\n\n/** A heading-only footer column and the heading-less column of links right after it, which it heads. */\ninterface HeadingOverLinks {\n heading: string\n links: string\n}\n\nfunction inspect(map: NodeMap): AiLayoutFrameHollows & { rows: string[]; merges: HeadingOverLinks[] } {\n const subtree = (id: string): string[] => {\n const out: string[] = []\n const visit = (at: string) => {\n if (!map[at] || out.includes(at)) return\n out.push(at)\n for (const child of map[at].nodes ?? []) visit(child)\n }\n visit(id)\n return out\n }\n /** The leaves under a node, in document order: what a visitor reads. */\n const leaves = (id: string) =>\n subtree(id).filter((at) => {\n const node = map[at]\n return !(node.nodes ?? []).some((child) => map[child]) && !HOLDERS.has(String(node.componentId))\n })\n const footers = Object.keys(map).filter((id) => renders(map[id], 'footer'))\n const headers = Object.keys(map).filter((id) => map[id].componentId === 'muiAppBar' || renders(map[id], 'header'))\n\n const footerIds = new Set(footers.flatMap(subtree))\n const rows = [...footerIds].filter((id) => map[id].componentId === 'muiGrid' && map[id].props?.['container'])\n const columns = rows.flatMap((row) => (map[row].nodes ?? []).filter((id) => map[id]?.componentId === 'muiGrid'))\n const footerColumns = columns.filter((id) => leaves(id).every((leaf) => isHeading(map[leaf])))\n // The Tidewater shape: a column of only a heading, then a column of only\n // links with no heading. The links are what the heading heads.\n const merges: HeadingOverLinks[] = []\n for (const row of rows) {\n const inRow = (map[row].nodes ?? []).filter((id) => map[id]?.componentId === 'muiGrid')\n inRow.forEach((id, at) => {\n const next = inRow[at + 1]\n if (!next || !footerColumns.includes(id) || !leaves(id).length) return\n const read = leaves(next)\n if (read.length && read.every((leaf) => map[leaf].componentId === LINK)) merges.push({ heading: id, links: next })\n })\n }\n // A heading is hollow when nothing but headings follow it before the next\n // heading of its own (an overline labels the heading after it, so it\n // reaches one heading further).\n const footerHeadings: string[] = []\n const flows = columns.length ? columns : footers\n for (const flow of flows) {\n if (footerColumns.includes(flow)) continue\n const read = leaves(flow)\n read.forEach((id, at) => {\n if (!isHeading(map[id])) return\n let headingsLeft = map[id].props?.['variant'] === 'overline' ? 2 : 1\n for (const next of read.slice(at + 1)) {\n if (!isHeading(map[next])) return\n if (map[next].props?.['variant'] !== 'overline') headingsLeft -= 1\n if (headingsLeft <= 0) break\n }\n footerHeadings.push(id)\n })\n }\n const footerCenteredRows = rows.filter((id) => map[id].sx?.['alignItems'] === 'center')\n const headerIds = new Set(headers.flatMap(subtree))\n const headerMenus = [...headerIds].filter(\n (id) => DROPDOWNS.has(String(map[id].componentId)) && !subtree(id).some((at) => at !== id && map[at].componentId === LINK),\n )\n return { footerColumns, footerHeadings, footerCenteredRows, headerMenus, rows, merges }\n}\n\n/** What is still hollow in a frame (AGL-3735); every list empty when nothing is. */\nexport function aiLayoutFrameHollows(nodes: Readonly<Record<string, unknown>>): AiLayoutFrameHollows {\n const { footerColumns, footerHeadings, footerCenteredRows, headerMenus } = inspect(nodes as NodeMap)\n return { footerColumns, footerHeadings, footerCenteredRows, headerMenus }\n}\n\n/** Whether a frame has anything hollow in it. */\nexport function aiLayoutFrameIsHollow(hollows: AiLayoutFrameHollows): boolean {\n return [hollows.footerColumns, hollows.footerHeadings, hollows.footerCenteredRows, hollows.headerMenus].some((ids) => ids.length > 0)\n}\n\n/** The sentences a check reports for what is hollow. */\nexport function aiLayoutFrameHollowsSaid(hollows: AiLayoutFrameHollows): string[] {\n return [\n ...(hollows.footerColumns.length ? [`${hollows.footerColumns.length} footer column(s) hold only a heading or nothing`] : []),\n ...(hollows.footerHeadings.length ? [`${hollows.footerHeadings.length} footer heading(s) have nothing under them`] : []),\n ...(hollows.footerCenteredRows.length ? ['the footer’s columns are centered, not top-aligned'] : []),\n ...(hollows.headerMenus.length ? [`${hollows.headerMenus.length} header dropdown(s) hold no link`] : []),\n ]\n}\n\n/**\n * Takes what is hollow out of a frame, IN PLACE (AGL-3735): see the file's\n * comment. Returns whether anything changed. A frame left holding nothing is\n * taken out after it, up to the row, the footer's or the header's own element.\n */\nexport function aiLayoutRepairFrameHollows(nodes: Record<string, unknown>): boolean {\n const map = nodes as NodeMap\n let changed = false\n const parentOf = (id: string) => Object.keys(map).find((at) => map[at].nodes?.includes(id)) ?? null\n const remove = (id: string) => {\n const tree = (at: string): string[] => [at, ...(map[at]?.nodes ?? []).flatMap(tree)]\n let parent = parentOf(id)\n for (const at of tree(id)) delete map[at]\n // Up the tree: a frame that held only what went is gone with it.\n let child = id\n while (parent && map[parent]) {\n const node = map[parent]\n node.nodes = (node.nodes ?? []).filter((at) => at !== child)\n const empty = !node.nodes.some((at) => map[at])\n const stops = renders(node, 'footer') || renders(node, 'header') || node.componentId === 'muiAppBar' || (node.componentId === 'muiGrid' && node.props?.['container'])\n if (!empty || stops || !HOLDERS.has(String(node.componentId))) break\n child = parent\n parent = parentOf(parent)\n delete map[child]\n }\n changed = true\n }\n for (let pass = 0; pass < 4; pass += 1) {\n const hollows = inspect(map)\n if (!aiLayoutFrameIsHollow(hollows)) break\n for (const id of hollows.footerCenteredRows) {\n map[id].sx = { ...(map[id].sx ?? {}), alignItems: 'flex-start' }\n changed = true\n }\n for (const id of hollows.headerMenus) if (map[id]) remove(id)\n const headed = new Set<string>()\n for (const merge of hollows.merges) {\n if (map[merge.heading] && map[merge.links] && headOver(map, merge)) {\n headed.add(merge.heading)\n changed = true\n }\n }\n for (const id of hollows.footerHeadings) if (map[id]) remove(id)\n for (const id of hollows.footerColumns) if (map[id] && !headed.has(id)) remove(id)\n // Every row that lost a column, however it lost it, reflows.\n for (const row of hollows.rows) if (map[row] && reflow(map, row)) changed = true\n }\n return changed\n}\n\n/**\n * A heading-only column takes in the heading-less column of links after it,\n * as one flow (heading, then links), and the links' column goes; whether it\n * changed. The row it leaves one column short reflows after.\n */\nfunction headOver(map: NodeMap, { heading, links }: HeadingOverLinks): boolean {\n const moved = (map[links].nodes ?? []).filter((id) => map[id])\n if (!moved.length) return false\n const own = (map[heading].nodes ?? []).filter((id) => map[id])\n const flow = own.length === 1 && map[own[0]].componentId === 'muiStack' ? own[0] : null\n if (flow) {\n map[flow].nodes = [...(map[flow].nodes ?? []), ...moved]\n } else {\n let id = `${heading}-flow`\n for (let n = 2; map[id]; n += 1) id = `${heading}-flow${n}`\n // The compiler's flow of a column (`compileFlow`): a stack, 2.5 apart.\n map[id] = { componentId: 'muiStack', props: { spacing: '2.5' }, nodes: [...own, ...moved] }\n map[heading].nodes = [id]\n }\n delete map[links]\n for (const node of Object.values(map)) {\n if (node.nodes?.includes(links)) node.nodes = node.nodes.filter((at) => at !== links)\n }\n return true\n}\n\n/** The columns a row has left, spread across its twelve as their widths were; whether it changed. */\nfunction reflow(map: NodeMap, row: string): boolean {\n const columns = (map[row].nodes ?? []).filter((id) => map[id]?.componentId === 'muiGrid')\n const md = columns.map((id) => /(?:^|\\s)md:(\\d+)/.exec(String(map[id].props?.['size'] ?? ''))?.[1])\n if (!columns.length || md.some((span) => !span)) return false\n // A row whose columns fill it, as many as it was drawn for, is as it was drawn.\n const drawnFor = /sm:6/.test(String(map[columns[0]].props?.['size'] ?? '')) ? 4 : 3\n if (md.reduce((sum, span) => sum + Number(span), 0) === 12 && (columns.length >= 4) === (drawnFor === 4)) return false\n const spans = columns.length === 1 ? [12] : aiLayoutSpans(md.map(Number))\n columns.forEach((id, at) => {\n map[id].props = { ...(map[id].props ?? {}), size: `${columns.length >= 4 ? 'xs:12 sm:6' : 'xs:12'} md:${spans[at]}` }\n })\n // The compiler's row spacing for that many columns (`compileRow`).\n if (map[row].props?.['spacing'] === '4' || map[row].props?.['spacing'] === '6') {\n map[row].props = { ...map[row].props, spacing: columns.length >= 3 ? '4' : '6' }\n }\n return true\n}\n\n/** A frame with what is hollow taken out (AGL-3735), as a copy; `null` when nothing was. */\nexport function aiLayoutWithoutFrameHollows<T extends Readonly<Record<string, unknown>>>(nodes: T): T | null {\n const copy: Record<string, unknown> = {}\n for (const [id, raw] of Object.entries(nodes)) {\n const node = raw as HollowNode\n copy[id] =\n node && typeof node === 'object'\n ? { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.sx ? { sx: { ...node.sx } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n : node\n }\n return aiLayoutRepairFrameHollows(copy) ? (copy as T) : null\n}\n"],"names":["AI_LAYOUT_FRAMES","aiLayoutSpans","HEADING_VARIANTS","Set","DROPDOWNS","LINK","HOLDERS","renders","node","element","props","componentId","isHeading","has","String","inspect","map","subtree","id","out","visit","at","includes","push","child","nodes","leaves","filter","some","footers","Object","keys","headers","footerIds","flatMap","rows","columns","row","footerColumns","every","leaf","merges","inRow","forEach","next","length","read","heading","links","footerHeadings","flows","flow","headingsLeft","slice","footerCenteredRows","sx","headerIds","headerMenus","aiLayoutFrameHollows","aiLayoutFrameIsHollow","hollows","ids","aiLayoutFrameHollowsSaid","aiLayoutRepairFrameHollows","changed","parentOf","find","remove","tree","parent","empty","stops","pass","alignItems","headed","merge","headOver","add","reflow","moved","own","n","spacing","values","md","exec","span","drawnFor","test","reduce","sum","Number","spans","size","aiLayoutWithoutFrameHollows","copy","raw","entries"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { AiStoreFrameLinks } from '../model/ai-site-store-pages';
18
- import { type AiLayoutSection, type AiLayoutSettlement } from './ai-layout-language';
18
+ import { type AiLayoutBlock, type AiLayoutSection, type AiLayoutSettlement } from './ai-layout-language';
19
19
  import { type AiLayoutPage, type AiLayoutTargets } from './ai-layout-links';
20
20
  import { type AiLayoutRawTree } from './ai-layout-tree';
21
21
  /**
@@ -66,13 +66,15 @@ export interface AiLayoutFramePlan {
66
66
  * linked by their addresses, as the blog is.
67
67
  */
68
68
  storeLinks?: AiStoreFrameLinks | null;
69
+ /** The site's language (AGL-3760): the frame's own words are in it where `ai-layout-labels.ts` lists it. */
70
+ language?: string;
69
71
  }
70
- /** What the header's link to a store's account page says (AGL-3676). */
71
- export declare const AI_LAYOUT_ACCOUNT_LINK = "Account";
72
+ /** What the header's link to a store's account page says (AGL-3676), in English. */
73
+ export declare const AI_LAYOUT_ACCOUNT_LINK: "Account";
72
74
  /** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */
73
- export declare const AI_LAYOUT_STORE_ANNOUNCEMENT = "Shop the full collection";
75
+ export declare const AI_LAYOUT_STORE_ANNOUNCEMENT: "Shop the full collection";
74
76
  /** The way into the shop beside the design's own announcement (AGL-3676). */
75
- export declare const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = "Shop now";
77
+ export declare const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK: "Shop now";
76
78
  export interface AiLayoutCompiledFrame {
77
79
  tree: AiLayoutRawTree;
78
80
  settled: AiLayoutSettlement[];
@@ -84,3 +86,20 @@ export declare function aiCompileLayoutFrame(frame: {
84
86
  header: AiLayoutSection | null;
85
87
  footer: AiLayoutSection | null;
86
88
  }, plan: AiLayoutFramePlan, targets: AiLayoutTargets): AiLayoutCompiledFrame;
89
+ /** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */
90
+ export declare const AI_LAYOUT_STORE_FOOTER_HEADING: "Customer care";
91
+ /**
92
+ * The footer heading a store's account and policy links go under
93
+ * (AGL-3735): a heading of the design's that names the store or its help
94
+ * ("Our shop", "Help", "Customer care"), the first with nothing under it
95
+ * before one with words or links of its own. Never the heading over the
96
+ * site's pages, whose list is the site's navigation. `end` is where the
97
+ * links go: the end of what that heading heads.
98
+ */
99
+ export declare function aiLayoutFooterStoreHeading(columns: readonly AiLayoutBlock[][], navItems: readonly {
100
+ to: string;
101
+ }[]): {
102
+ column: AiLayoutBlock[];
103
+ end: number;
104
+ heading: string;
105
+ } | null;
@@ -17,13 +17,15 @@
17
17
  import { AI_ICON_LIBRARY } from "../runtime/ai-icon-library.js";
18
18
  import { AI_LAYOUT_MAX_IMAGES, bandSx, compileFlow, compileRow, destinationProps } from "./ai-layout-compiler.js";
19
19
  import { aiLayoutFitText } from "./ai-layout-copy.js";
20
+ import { aiLayoutRepairFrameHollows } from "./ai-layout-frame-hollows.js";
20
21
  import { AI_LAYOUT_CART_ELEMENT } from "./ai-layout-listings.js";
21
22
  import { AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
23
+ import { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from "./ai-layout-labels.js";
22
24
  import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
23
25
  import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
24
- /** What the header's link to a store's account page says (AGL-3676). */ export const AI_LAYOUT_ACCOUNT_LINK = 'Account';
25
- /** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */ export const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection';
26
- /** The way into the shop beside the design's own announcement (AGL-3676). */ export const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now';
26
+ /** What the header's link to a store's account page says (AGL-3676), in English. */ export const AI_LAYOUT_ACCOUNT_LINK = AI_LAYOUT_LABELS_EN.account;
27
+ /** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */ export const AI_LAYOUT_STORE_ANNOUNCEMENT = AI_LAYOUT_LABELS_EN.storeAnnouncement;
28
+ /** The way into the shop beside the design's own announcement (AGL-3676). */ export const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = AI_LAYOUT_LABELS_EN.shopNow;
27
29
  /** The header's colors by band, as palette roles. */ function headerLook(band) {
28
30
  if (band === 'brand') return {
29
31
  props: {
@@ -112,9 +114,11 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
112
114
  pageId: null
113
115
  }),
114
116
  // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.
115
- options: {
117
+ options: _extends({
116
118
  reusableComponents: true
117
- },
119
+ }, plan.language ? {
120
+ language: plan.language
121
+ } : {}),
118
122
  settled: [],
119
123
  scrollTo: {},
120
124
  // A frame's lines are its own, never a page section's items.
@@ -240,7 +244,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
240
244
  const accountPath = (_ref1 = (_plan_storeLinks1 = plan.storeLinks) == null ? void 0 : _plan_storeLinks1.account) != null ? _ref1 : null;
241
245
  const accountLink = (role, interactions, sx)=>{
242
246
  const id = tree.add('muiScreenLink', {
243
- children: AI_LAYOUT_ACCOUNT_LINK,
247
+ children: aiLayoutLabel('account', plan.language),
244
248
  href: accountPath,
245
249
  renderAs: 'link',
246
250
  color: 'inherit'
@@ -352,7 +356,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
352
356
  });
353
357
  // The design's line, then the way into the shop; else the way in alone.
354
358
  const link = tree.add('muiScreenLink', {
355
- children: said ? AI_LAYOUT_STORE_ANNOUNCEMENT_LINK : AI_LAYOUT_STORE_ANNOUNCEMENT,
359
+ children: aiLayoutLabel(said ? 'shopNow' : 'storeAnnouncement', plan.language),
356
360
  screenId: plan.shopId,
357
361
  renderAs: 'link',
358
362
  color: 'inherit'
@@ -390,6 +394,14 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
390
394
  flexGrow: 1
391
395
  }, null, 'slot');
392
396
  const footer = compileFooter(page, frame.footer, plan, name);
397
+ // No heading with nothing under it, no column of only a heading, columns
398
+ // top-aligned (AGL-3735): a list under a heading can compile to nothing.
399
+ if (aiLayoutRepairFrameHollows(tree.nodes)) {
400
+ page.settled.push({
401
+ at: 'footer',
402
+ what: 'a heading with nothing under it taken out, and its column with it'
403
+ });
404
+ }
393
405
  const column = tree.add('muiStack', null, {
394
406
  minHeight: '100vh'
395
407
  }, [
@@ -412,6 +424,49 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
412
424
  settled: page.settled
413
425
  };
414
426
  }
427
+ /** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */ export const AI_LAYOUT_STORE_FOOTER_HEADING = AI_LAYOUT_LABELS_EN.customerCare;
428
+ /** A footer heading a store's account and policies belong under (AGL-3735). */ const STORE_HEADING = /\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\b/i;
429
+ /**
430
+ * The footer heading a store's account and policy links go under
431
+ * (AGL-3735): a heading of the design's that names the store or its help
432
+ * ("Our shop", "Help", "Customer care"), the first with nothing under it
433
+ * before one with words or links of its own. Never the heading over the
434
+ * site's pages, whose list is the site's navigation. `end` is where the
435
+ * links go: the end of what that heading heads.
436
+ */ export function aiLayoutFooterStoreHeading(columns, navItems) {
437
+ var _candidates_find;
438
+ const navTo = new Set(navItems.map((item)=>item.to));
439
+ const candidates = [];
440
+ for (const column of columns){
441
+ column.forEach((block, at)=>{
442
+ var _block_text;
443
+ if (block.kind !== 'heading' || !STORE_HEADING.test((_block_text = block.text) != null ? _block_text : '')) return;
444
+ let end = at + 1;
445
+ while(end < column.length && column[end].kind !== 'heading')end += 1;
446
+ const under = column.slice(at + 1, end);
447
+ const site = under.some((one)=>{
448
+ var _one_items;
449
+ return one.kind === 'list' && ((_one_items = one.items) != null ? _one_items : []).some((item)=>{
450
+ var _item_to;
451
+ return navTo.has((_item_to = item.to) != null ? _item_to : '');
452
+ });
453
+ });
454
+ if (site) return;
455
+ candidates.push({
456
+ column: column,
457
+ end,
458
+ heading: String(block.text),
459
+ empty: under.length === 0
460
+ });
461
+ });
462
+ }
463
+ const pick = (_candidates_find = candidates.find((one)=>one.empty)) != null ? _candidates_find : candidates[0];
464
+ return pick ? {
465
+ column: pick.column,
466
+ end: pick.end,
467
+ heading: pick.heading
468
+ } : null;
469
+ }
415
470
  /** A link followed from the phone menu closes it. */ const AI_LAYOUT_CLOSE_DRAWER = {
416
471
  id: 'ai-close-menu',
417
472
  name: 'Close the menu',
@@ -522,20 +577,37 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
522
577
  ]);
523
578
  weights.push(1);
524
579
  }
525
- // A store's account and policies (AGL-3676), as one more list beside the site's pages.
580
+ // A store's account and policies (AGL-3676): under the store heading the
581
+ // design already wrote ("Our shop", "Help"…), never as a column with no
582
+ // heading beside a heading with nothing under it (AGL-3735); where the
583
+ // design wrote none, a column of their own, headed.
526
584
  const storeLinks = (_ref2 = (_plan_storeLinks = plan.storeLinks) == null ? void 0 : _plan_storeLinks.footer) != null ? _ref2 : [];
527
585
  if (storeLinks.length) {
528
- columns.push([
529
- {
530
- kind: 'list',
531
- items: storeLinks.map((link)=>({
532
- title: link.label,
533
- text: '',
534
- to: `page:store:${link.href}`
535
- }))
536
- }
537
- ]);
538
- weights.push(1);
586
+ const list = {
587
+ kind: 'list',
588
+ items: storeLinks.map((link)=>({
589
+ title: link.label,
590
+ text: '',
591
+ to: `page:store:${link.href}`
592
+ }))
593
+ };
594
+ const home = aiLayoutFooterStoreHeading(columns, navItems);
595
+ if (home) {
596
+ home.column.splice(home.end, 0, list);
597
+ page.settled.push({
598
+ at: 'footer',
599
+ what: `the store's account and policy links under "${home.heading}"`
600
+ });
601
+ } else {
602
+ columns.push([
603
+ {
604
+ kind: 'heading',
605
+ text: aiLayoutLabel('customerCare', plan.language)
606
+ },
607
+ list
608
+ ]);
609
+ weights.push(1);
610
+ }
539
611
  }
540
612
  const filled = columns.map((column, position)=>{
541
613
  var _weights_position;
@@ -544,7 +616,8 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
544
616
  weight: (_weights_position = weights[position]) != null ? _weights_position : 1
545
617
  };
546
618
  }).filter((entry)=>entry.column.length);
547
- const top = filled.length > 1 ? compileRow(scope, filled) : filled.length === 1 ? compileFlow(scope, filled[0].column, 'full') : null;
619
+ // A footer's columns are top-aligned: each starts at its heading (AGL-3735).
620
+ const top = filled.length > 1 ? compileRow(scope, filled, 'flex-start') : filled.length === 1 ? compileFlow(scope, filled[0].column, 'full') : null;
548
621
  const copyright = tree.add('muiTypography', {
549
622
  children: `© ${name}`,
550
623
  variant: 'body2',