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

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 (116) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +3 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +7 -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.js +6 -4
  13. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  14. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/jobs/ai-build-capabilities.js +162 -2
  17. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
  19. package/src/lib/jobs/ai-job-build-step.js +85 -16
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  22. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  24. package/src/lib/jobs/ai-job-layout-language.js +13 -0
  25. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  26. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  27. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  28. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  29. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  30. package/src/lib/jobs/ai-job-page-language.js +21 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-page-sections.js +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
  35. package/src/lib/jobs/ai-job-plan-step.js +16 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  38. package/src/lib/jobs/ai-job-products-step.js +2 -2
  39. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
  41. package/src/lib/jobs/ai-job-settings-step.js +695 -0
  42. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  43. package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
  44. package/src/lib/jobs/ai-job-site-content.js +28 -12
  45. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  46. package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
  47. package/src/lib/jobs/ai-job-site-datasets.js +10 -3
  48. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  49. package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
  50. package/src/lib/jobs/ai-job-site-step.js +70 -10
  51. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  53. package/src/lib/jobs/ai-site-publish.js +203 -56
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
  56. package/src/lib/layout-language/ai-layout-compiler.js +26 -25
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  59. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  60. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
  62. package/src/lib/layout-language/ai-layout-frame.js +83 -13
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +34 -0
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  68. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  69. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  70. package/src/lib/model/ai-build-job.d.ts +23 -0
  71. package/src/lib/model/ai-build-job.js +102 -13
  72. package/src/lib/model/ai-build-job.js.map +1 -1
  73. package/src/lib/model/ai-build-plan.d.ts +25 -0
  74. package/src/lib/model/ai-build-plan.js +45 -1
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  77. package/src/lib/model/ai-credit-estimate.js +51 -0
  78. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  79. package/src/lib/model/ai-job-activity.js +4 -1
  80. package/src/lib/model/ai-job-activity.js.map +1 -1
  81. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  82. package/src/lib/model/ai-jobs.types.js +4 -1
  83. package/src/lib/model/ai-jobs.types.js.map +1 -1
  84. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  85. package/src/lib/model/ai-layout-menus.js +518 -0
  86. package/src/lib/model/ai-layout-menus.js.map +1 -0
  87. package/src/lib/model/ai-products.d.ts +31 -1
  88. package/src/lib/model/ai-products.js +34 -1
  89. package/src/lib/model/ai-products.js.map +1 -1
  90. package/src/lib/model/ai-site-inventory.d.ts +15 -0
  91. package/src/lib/model/ai-site-inventory.js.map +1 -1
  92. package/src/lib/model/ai-site-job.d.ts +10 -0
  93. package/src/lib/model/ai-site-job.js +36 -9
  94. package/src/lib/model/ai-site-job.js.map +1 -1
  95. package/src/lib/model/ai-site-store-pages.js +1 -1
  96. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  97. package/src/lib/runtime/ai-doctrine-validators.d.ts +53 -0
  98. package/src/lib/runtime/ai-doctrine-validators.js +166 -25
  99. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  100. package/src/lib/runtime/ai-doctrine.js +7 -2
  101. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  102. package/src/lib/runtime/ai-palette.generated.js +1 -1
  103. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  104. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  105. package/src/lib/runtime/ai-products-generation.js +12 -6
  106. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  107. package/src/lib/runtime/site-inventory.d.ts +12 -0
  108. package/src/lib/runtime/site-inventory.js +29 -1
  109. package/src/lib/runtime/site-inventory.js.map +1 -1
  110. package/src/lib/server.js +12 -0
  111. package/src/lib/server.js.map +1 -1
  112. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  113. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  114. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  115. package/src/lib/tools/ai-products-tool.js +15 -5
  116. 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
  /**
@@ -84,3 +84,20 @@ export declare function aiCompileLayoutFrame(frame: {
84
84
  header: AiLayoutSection | null;
85
85
  footer: AiLayoutSection | null;
86
86
  }, plan: AiLayoutFramePlan, targets: AiLayoutTargets): AiLayoutCompiledFrame;
87
+ /** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */
88
+ export declare const AI_LAYOUT_STORE_FOOTER_HEADING = "Customer care";
89
+ /**
90
+ * The footer heading a store's account and policy links go under
91
+ * (AGL-3735): a heading of the design's that names the store or its help
92
+ * ("Our shop", "Help", "Customer care"), the first with nothing under it
93
+ * before one with words or links of its own. Never the heading over the
94
+ * site's pages, whose list is the site's navigation. `end` is where the
95
+ * links go: the end of what that heading heads.
96
+ */
97
+ export declare function aiLayoutFooterStoreHeading(columns: readonly AiLayoutBlock[][], navItems: readonly {
98
+ to: string;
99
+ }[]): {
100
+ column: AiLayoutBlock[];
101
+ end: number;
102
+ heading: string;
103
+ } | null;
@@ -17,6 +17,7 @@
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";
22
23
  import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
@@ -390,6 +391,14 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
390
391
  flexGrow: 1
391
392
  }, null, 'slot');
392
393
  const footer = compileFooter(page, frame.footer, plan, name);
394
+ // No heading with nothing under it, no column of only a heading, columns
395
+ // top-aligned (AGL-3735): a list under a heading can compile to nothing.
396
+ if (aiLayoutRepairFrameHollows(tree.nodes)) {
397
+ page.settled.push({
398
+ at: 'footer',
399
+ what: 'a heading with nothing under it taken out, and its column with it'
400
+ });
401
+ }
393
402
  const column = tree.add('muiStack', null, {
394
403
  minHeight: '100vh'
395
404
  }, [
@@ -412,6 +421,49 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
412
421
  settled: page.settled
413
422
  };
414
423
  }
424
+ /** 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 = 'Customer care';
425
+ /** 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;
426
+ /**
427
+ * The footer heading a store's account and policy links go under
428
+ * (AGL-3735): a heading of the design's that names the store or its help
429
+ * ("Our shop", "Help", "Customer care"), the first with nothing under it
430
+ * before one with words or links of its own. Never the heading over the
431
+ * site's pages, whose list is the site's navigation. `end` is where the
432
+ * links go: the end of what that heading heads.
433
+ */ export function aiLayoutFooterStoreHeading(columns, navItems) {
434
+ var _candidates_find;
435
+ const navTo = new Set(navItems.map((item)=>item.to));
436
+ const candidates = [];
437
+ for (const column of columns){
438
+ column.forEach((block, at)=>{
439
+ var _block_text;
440
+ if (block.kind !== 'heading' || !STORE_HEADING.test((_block_text = block.text) != null ? _block_text : '')) return;
441
+ let end = at + 1;
442
+ while(end < column.length && column[end].kind !== 'heading')end += 1;
443
+ const under = column.slice(at + 1, end);
444
+ const site = under.some((one)=>{
445
+ var _one_items;
446
+ return one.kind === 'list' && ((_one_items = one.items) != null ? _one_items : []).some((item)=>{
447
+ var _item_to;
448
+ return navTo.has((_item_to = item.to) != null ? _item_to : '');
449
+ });
450
+ });
451
+ if (site) return;
452
+ candidates.push({
453
+ column: column,
454
+ end,
455
+ heading: String(block.text),
456
+ empty: under.length === 0
457
+ });
458
+ });
459
+ }
460
+ const pick = (_candidates_find = candidates.find((one)=>one.empty)) != null ? _candidates_find : candidates[0];
461
+ return pick ? {
462
+ column: pick.column,
463
+ end: pick.end,
464
+ heading: pick.heading
465
+ } : null;
466
+ }
415
467
  /** A link followed from the phone menu closes it. */ const AI_LAYOUT_CLOSE_DRAWER = {
416
468
  id: 'ai-close-menu',
417
469
  name: 'Close the menu',
@@ -522,20 +574,37 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
522
574
  ]);
523
575
  weights.push(1);
524
576
  }
525
- // A store's account and policies (AGL-3676), as one more list beside the site's pages.
577
+ // A store's account and policies (AGL-3676): under the store heading the
578
+ // design already wrote ("Our shop", "Help"…), never as a column with no
579
+ // heading beside a heading with nothing under it (AGL-3735); where the
580
+ // design wrote none, a column of their own, headed.
526
581
  const storeLinks = (_ref2 = (_plan_storeLinks = plan.storeLinks) == null ? void 0 : _plan_storeLinks.footer) != null ? _ref2 : [];
527
582
  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);
583
+ const list = {
584
+ kind: 'list',
585
+ items: storeLinks.map((link)=>({
586
+ title: link.label,
587
+ text: '',
588
+ to: `page:store:${link.href}`
589
+ }))
590
+ };
591
+ const home = aiLayoutFooterStoreHeading(columns, navItems);
592
+ if (home) {
593
+ home.column.splice(home.end, 0, list);
594
+ page.settled.push({
595
+ at: 'footer',
596
+ what: `the store's account and policy links under "${home.heading}"`
597
+ });
598
+ } else {
599
+ columns.push([
600
+ {
601
+ kind: 'heading',
602
+ text: AI_LAYOUT_STORE_FOOTER_HEADING
603
+ },
604
+ list
605
+ ]);
606
+ weights.push(1);
607
+ }
539
608
  }
540
609
  const filled = columns.map((column, position)=>{
541
610
  var _weights_position;
@@ -544,7 +613,8 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
544
613
  weight: (_weights_position = weights[position]) != null ? _weights_position : 1
545
614
  };
546
615
  }).filter((entry)=>entry.column.length);
547
- const top = filled.length > 1 ? compileRow(scope, filled) : filled.length === 1 ? compileFlow(scope, filled[0].column, 'full') : null;
616
+ // A footer's columns are top-aligned: each starts at its heading (AGL-3735).
617
+ const top = filled.length > 1 ? compileRow(scope, filled, 'flex-start') : filled.length === 1 ? compileFlow(scope, filled[0].column, 'full') : null;
548
618
  const copyright = tree.add('muiTypography', {
549
619
  children: `© ${name}`,
550
620
  variant: 'body2',