@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.231

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 (139) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
  3. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  4. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  5. package/src/lib/components/ai-assist-provider.component.js +9 -10
  6. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  7. package/src/lib/components/ai-job-plan.component.js +3 -1
  8. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  9. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  10. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  11. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  12. package/src/lib/components/ai-site-build-page.component.js +20 -8
  13. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  14. package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
  15. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  16. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  17. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  18. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  19. package/src/lib/jobs/ai-job-build-step.js +11 -0
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  22. package/src/lib/jobs/ai-job-generation.js +13 -2
  23. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  24. package/src/lib/jobs/ai-job-insight-step.js +8 -1
  25. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
  27. package/src/lib/jobs/ai-job-layout-language.js +194 -0
  28. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  29. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  30. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  31. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  32. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  33. package/src/lib/jobs/ai-job-layout-step.js +105 -5
  34. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
  36. package/src/lib/jobs/ai-job-page-language.js +355 -0
  37. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  38. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  39. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  40. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  41. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  42. package/src/lib/jobs/ai-job-page-step.js +105 -14
  43. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-plan-step.d.ts +1 -1
  45. package/src/lib/jobs/ai-job-plan-step.js +8 -2
  46. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  47. package/src/lib/jobs/ai-job-site-step.d.ts +4 -0
  48. package/src/lib/jobs/ai-job-site-step.js +20 -3
  49. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-workflow-step.js +2 -2
  51. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  52. package/src/lib/jobs/ai-jobs.d.ts +1 -1
  53. package/src/lib/jobs/ai-jobs.js +17 -8
  54. package/src/lib/jobs/ai-jobs.js.map +1 -1
  55. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  56. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  57. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  58. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  59. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  60. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
  62. package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  64. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  65. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  66. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  67. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  68. package/src/lib/layout-language/ai-layout-frame.js +457 -0
  69. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  70. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  71. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  72. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  73. package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
  74. package/src/lib/layout-language/ai-layout-language.js +446 -0
  75. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  76. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  77. package/src/lib/layout-language/ai-layout-links.js +109 -0
  78. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  79. package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
  80. package/src/lib/layout-language/ai-layout-store.js +69 -0
  81. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  82. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  83. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  84. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  85. package/src/lib/lazy-widget.d.ts +45 -0
  86. package/src/lib/lazy-widget.js +53 -0
  87. package/src/lib/lazy-widget.js.map +1 -0
  88. package/src/lib/model/ai-build-job.js +2 -1
  89. package/src/lib/model/ai-build-job.js.map +1 -1
  90. package/src/lib/model/ai-build-progress.d.ts +20 -3
  91. package/src/lib/model/ai-build-progress.js +48 -6
  92. package/src/lib/model/ai-build-progress.js.map +1 -1
  93. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  94. package/src/lib/model/ai-host-publish-context.js +104 -0
  95. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  96. package/src/lib/model/ai-job-activity.d.ts +22 -0
  97. package/src/lib/model/ai-job-activity.js +22 -1
  98. package/src/lib/model/ai-job-activity.js.map +1 -1
  99. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  100. package/src/lib/model/ai-job-failure-copy.js +56 -1
  101. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  102. package/src/lib/model/ai-job-notice.d.ts +3 -1
  103. package/src/lib/model/ai-job-notice.js +31 -9
  104. package/src/lib/model/ai-job-notice.js.map +1 -1
  105. package/src/lib/model/ai-jobs.types.d.ts +19 -0
  106. package/src/lib/model/ai-jobs.types.js.map +1 -1
  107. package/src/lib/model/ai-site-build-progress.d.ts +5 -1
  108. package/src/lib/model/ai-site-build-progress.js +27 -4
  109. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  110. package/src/lib/model/ai-site-job.d.ts +8 -0
  111. package/src/lib/model/ai-site-job.js +9 -0
  112. package/src/lib/model/ai-site-job.js.map +1 -1
  113. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  114. package/src/lib/model/ai-site-start.d.ts +5 -2
  115. package/src/lib/model/ai-site-start.js +5 -2
  116. package/src/lib/model/ai-site-start.js.map +1 -1
  117. package/src/lib/plugin.js +79 -47
  118. package/src/lib/plugin.js.map +1 -1
  119. package/src/lib/providers/catalog.d.ts +1 -1
  120. package/src/lib/providers/catalog.js +3 -3
  121. package/src/lib/providers/catalog.js.map +1 -1
  122. package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
  123. package/src/lib/runtime/ai-doctrine-validators.js +436 -4
  124. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  125. package/src/lib/runtime/ai-doctrine.js +12 -3
  126. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  127. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  128. package/src/lib/runtime/ai-icon-library.js +254 -0
  129. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  130. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  131. package/src/lib/runtime/ai-node-tree.js +30 -4
  132. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  133. package/src/lib/runtime/ai-palette.d.ts +6 -0
  134. package/src/lib/runtime/ai-palette.generated.js +70 -0
  135. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  136. package/src/lib/runtime/ai-palette.js.map +1 -1
  137. package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
  138. package/src/lib/tools/ai-workflow-tool.js +16 -14
  139. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
@@ -0,0 +1,345 @@
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
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { aiBracketedFacts } from "../runtime/ai-doctrine-validators.js";
18
+ import { aiStoredScrollTargets } from "../runtime/ai-page-links.js";
19
+ /** Props that hold no words a visitor reads: an icon's drawing is path data. */ const NOT_WORDS = new Set([
20
+ 'iconPath',
21
+ 'iconId'
22
+ ]);
23
+ /** The props a node's own words are in. */ const WORD_PROPS = [
24
+ 'children',
25
+ 'primary',
26
+ 'secondary',
27
+ 'label'
28
+ ];
29
+ /** Elements that say something with no words of their own. */ const SPEAKS_ALONE = new Set([
30
+ 'image',
31
+ 'form',
32
+ 'layoutSlot',
33
+ 'reusableInstance',
34
+ 'muiDrawerToggle',
35
+ 'muiDrawer'
36
+ ]);
37
+ const HEADING_VARIANTS = new Set([
38
+ 'h1',
39
+ 'h2',
40
+ 'h3',
41
+ 'h4',
42
+ 'h5',
43
+ 'h6',
44
+ 'overline'
45
+ ]);
46
+ /** The violation code a gap is named by. */ export const AI_LAYOUT_GAP_CODE = 'layout-gap';
47
+ /** Every string a value holds, however deep, its icon drawings aside. */ function stringsOf(value, key = '') {
48
+ if (NOT_WORDS.has(key)) return [];
49
+ if (typeof value === 'string') return [
50
+ value
51
+ ];
52
+ if (Array.isArray(value)) return value.flatMap((entry)=>stringsOf(entry));
53
+ if (value && typeof value === 'object') {
54
+ return Object.entries(value).flatMap(([name, entry])=>stringsOf(entry, name));
55
+ }
56
+ return [];
57
+ }
58
+ /** Each node whose words carry a gap, with the gaps it carries. */ function gapsOf(nodes) {
59
+ return Object.entries(nodes).flatMap(([id, node])=>{
60
+ var _node_props;
61
+ const texts = stringsOf((_node_props = node.props) != null ? _node_props : {});
62
+ const facts = aiBracketedFacts(texts);
63
+ return facts.length ? [
64
+ {
65
+ id,
66
+ facts,
67
+ text: texts.filter((text)=>aiBracketedFacts([
68
+ text
69
+ ]).length).join(' ')
70
+ }
71
+ ] : [];
72
+ });
73
+ }
74
+ /** The gaps a language-built page or frame carries, as the violation that asks again. */ export function aiLayoutGapViolations(nodes) {
75
+ const found = gapsOf(nodes);
76
+ if (!found.length) return [];
77
+ const facts = aiBracketedFacts(found.flatMap((entry)=>entry.facts));
78
+ return [
79
+ {
80
+ rule: 14,
81
+ code: AI_LAYOUT_GAP_CODE,
82
+ message: `${facts.join(', ')} ${facts.length === 1 ? 'is a gap' : 'are gaps'} for a fact the brief does not give, and this site is published exactly as written. Leave each fact out: write the sentence without it, and make no list line, card or contact row for it. Never write a gap in square brackets, and never invent the fact.`,
83
+ nodeIds: found.map((entry)=>entry.id)
84
+ }
85
+ ];
86
+ }
87
+ function parentsOf(nodes) {
88
+ var _node_nodes;
89
+ const parents = new Map();
90
+ for (const [id, node] of Object.entries(nodes))for (const child of (_node_nodes = node.nodes) != null ? _node_nodes : [])parents.set(child, id);
91
+ return parents;
92
+ }
93
+ function subtree(nodes, id) {
94
+ const ids = [];
95
+ const stack = [
96
+ id
97
+ ];
98
+ while(stack.length){
99
+ var _nodes_next_nodes;
100
+ const next = stack.pop();
101
+ if (!nodes[next] || ids.includes(next)) continue;
102
+ ids.push(next);
103
+ stack.push(...(_nodes_next_nodes = nodes[next].nodes) != null ? _nodes_next_nodes : []);
104
+ }
105
+ return ids;
106
+ }
107
+ function wordsOf(node) {
108
+ for (const prop of WORD_PROPS){
109
+ var _node_props;
110
+ const value = (_node_props = node.props) == null ? void 0 : _node_props[prop];
111
+ if (typeof value === 'string' && value.trim()) return value.trim();
112
+ }
113
+ return '';
114
+ }
115
+ function isHeading(node) {
116
+ var _ref, _ref1;
117
+ var _node_props, _node_props1;
118
+ if (node.componentId !== 'muiTypography') return false;
119
+ return HEADING_VARIANTS.has(String((_ref = (_node_props = node.props) == null ? void 0 : _node_props['variant']) != null ? _ref : '')) || /^h[1-6]$/.test(String((_ref1 = (_node_props1 = node.props) == null ? void 0 : _node_props1['component']) != null ? _ref1 : ''));
120
+ }
121
+ /** Whether anything under a node says something; with `beyondHeadings`, anything but a heading. */ function speaks(nodes, id, beyondHeadings = false) {
122
+ return subtree(nodes, id).some((each)=>{
123
+ const node = nodes[each];
124
+ const says = SPEAKS_ALONE.has(node.componentId) || !!wordsOf(node);
125
+ return says && !(beyondHeadings && isHeading(node));
126
+ });
127
+ }
128
+ /** A node's props, its component's prop values copied too, so a rewrite never reaches the input. */ function copyProps(props) {
129
+ const values = props['propValues'];
130
+ return values && typeof values === 'object' && !Array.isArray(values) ? _extends({}, props, {
131
+ propValues: _extends({}, values)
132
+ }) : _extends({}, props);
133
+ }
134
+ function cloneNodes(input) {
135
+ return Object.fromEntries(Object.entries(input).map(([id, node])=>[
136
+ id,
137
+ _extends({}, node, node.props ? {
138
+ props: copyProps(node.props)
139
+ } : {}, node.nodes ? {
140
+ nodes: [
141
+ ...node.nodes
142
+ ]
143
+ } : {})
144
+ ]));
145
+ }
146
+ /**
147
+ * Takes each found node out, and each element above it left with nothing to
148
+ * say, up to its section; a section other than the page's first left with
149
+ * only its heading goes too, with any link that scrolled to it.
150
+ */ function pruneFound(nodes, rootId, found) {
151
+ var _ref;
152
+ var _nodes_rootId, _nodes_rootId_nodes, _nodes_rootId1;
153
+ const dropped = [];
154
+ const sections = new Set((_ref = (_nodes_rootId = nodes[rootId]) == null ? void 0 : _nodes_rootId.nodes) != null ? _ref : []);
155
+ const touched = new Set();
156
+ const remove = (id)=>{
157
+ var _nodes_parent_nodes;
158
+ const parent = parentsOf(nodes).get(id);
159
+ for (const each of subtree(nodes, id))delete nodes[each];
160
+ if (parent && nodes[parent]) nodes[parent].nodes = ((_nodes_parent_nodes = nodes[parent].nodes) != null ? _nodes_parent_nodes : []).filter((child)=>child !== id);
161
+ };
162
+ /** Takes a node out, and each element above it it leaves with nothing to say, short of a section. */ const prune = (id)=>{
163
+ const parents = parentsOf(nodes);
164
+ let section = id;
165
+ while(section && !sections.has(section))section = parents.get(section);
166
+ if (section && section !== id) touched.add(section);
167
+ let parent = parents.get(id);
168
+ remove(id);
169
+ while(parent && parent !== rootId && nodes[parent]){
170
+ if (sections.has(parent) || speaks(nodes, parent)) break;
171
+ const above = parentsOf(nodes).get(parent);
172
+ remove(parent);
173
+ parent = above;
174
+ }
175
+ };
176
+ for (const entry of found){
177
+ if (!nodes[entry.id]) continue;
178
+ dropped.push(entry.text);
179
+ prune(entry.id);
180
+ }
181
+ const first = (_nodes_rootId1 = nodes[rootId]) == null ? void 0 : (_nodes_rootId_nodes = _nodes_rootId1.nodes) == null ? void 0 : _nodes_rootId_nodes[0];
182
+ for (const section of touched){
183
+ var _ref1;
184
+ var _nodes_section_props;
185
+ if (section === first || !nodes[section] || speaks(nodes, section, true)) continue;
186
+ const label = String((_ref1 = (_nodes_section_props = nodes[section].props) == null ? void 0 : _nodes_section_props['ariaLabel']) != null ? _ref1 : section);
187
+ remove(section);
188
+ dropped.push(`the section "${label}", left with only its heading`);
189
+ for (const [id, node] of Object.entries(nodes)){
190
+ if (nodes[id] && aiStoredScrollTargets(node).includes(section)) {
191
+ dropped.push(`"${wordsOf(node)}", a link to that section`);
192
+ prune(id);
193
+ }
194
+ }
195
+ }
196
+ return dropped;
197
+ }
198
+ /** The page or frame with every gap taken out, and what was taken. */ export function aiLayoutWithoutGaps(input, rootId) {
199
+ const found = gapsOf(input);
200
+ if (!found.length) return {
201
+ nodes: input,
202
+ dropped: []
203
+ };
204
+ const nodes = cloneNodes(input);
205
+ const dropped = pruneFound(nodes, rootId, found);
206
+ return {
207
+ nodes: nodes,
208
+ dropped
209
+ };
210
+ }
211
+ /** The violation code an internal reference in copy is named by. */ export const AI_LAYOUT_REF_CODE = 'layout-internal-ref';
212
+ /** A link's own notation, or a template token other than the site's own (`{{host.…}}`). */ const REF = /\b(?:page|new|form|component|screen):[A-Za-z0-9_-]+|\{\{(?!\s*host\.)[^{}]*\}\}/g;
213
+ const PAGE_REF = /\bpage:([A-Za-z0-9_-]+)/g;
214
+ /** The props whose words a visitor reads, and a component's prop values. */ const SHOWN_PROPS = [
215
+ ...WORD_PROPS,
216
+ 'alt',
217
+ 'title'
218
+ ];
219
+ function shownTexts(node) {
220
+ var _node_props;
221
+ const props = (_node_props = node.props) != null ? _node_props : {};
222
+ const slots = [];
223
+ for (const prop of SHOWN_PROPS){
224
+ if (typeof props[prop] === 'string') slots.push({
225
+ get: ()=>props[prop],
226
+ set: (value)=>props[prop] = value
227
+ });
228
+ }
229
+ const values = props['propValues'];
230
+ if (values && typeof values === 'object' && !Array.isArray(values)) {
231
+ const record = values;
232
+ for (const name of Object.keys(record)){
233
+ if (typeof record[name] === 'string') slots.push({
234
+ get: ()=>record[name],
235
+ set: (value)=>record[name] = value
236
+ });
237
+ }
238
+ }
239
+ return slots;
240
+ }
241
+ function escapeRegExp(value) {
242
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
243
+ }
244
+ function refsIn(text, names) {
245
+ var _text_match;
246
+ const found = ((_text_match = text.match(REF)) != null ? _text_match : []).map((ref)=>ref.trim());
247
+ for (const id of names.ids){
248
+ if (id.length >= 6 && new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(id)}($|[^A-Za-z0-9_-])`).test(text)) found.push(id);
249
+ }
250
+ return [
251
+ ...new Set(found)
252
+ ];
253
+ }
254
+ function refsOf(nodes, names) {
255
+ return Object.entries(nodes).flatMap(([id, node])=>{
256
+ const texts = shownTexts(node).map((slot)=>slot.get());
257
+ const refs = [
258
+ ...new Set(texts.flatMap((text)=>refsIn(text, names)))
259
+ ];
260
+ return refs.length ? [
261
+ {
262
+ id,
263
+ refs,
264
+ text: texts.filter((text)=>refsIn(text, names).length).join(' ')
265
+ }
266
+ ] : [];
267
+ });
268
+ }
269
+ /** Internal references in a language-built page's or frame's words, as the violation that asks again. */ export function aiLayoutRefViolations(nodes, names) {
270
+ const found = refsOf(nodes, names);
271
+ if (!found.length) return [];
272
+ const refs = [
273
+ ...new Set(found.flatMap((entry)=>entry.refs))
274
+ ].map((ref)=>`"${ref}"`);
275
+ return [
276
+ {
277
+ rule: 14,
278
+ code: AI_LAYOUT_REF_CODE,
279
+ message: `${refs.join(', ')} ${refs.length === 1 ? 'is a link reference' : 'are link references'} written into words a visitor reads. A reference goes only in a button's or a list item's to; in text, name the page by its title, or leave the reference out.`,
280
+ nodeIds: found.map((entry)=>entry.id)
281
+ }
282
+ ];
283
+ }
284
+ /**
285
+ * The page or frame with no internal reference in its words: a page's
286
+ * reference is written as that page's title, and a line that still names a
287
+ * reference is taken out, as a gap is. Returns what was rewritten and taken.
288
+ */ export function aiLayoutWithoutRefs(input, rootId, names) {
289
+ if (!refsOf(input, names).length) return {
290
+ nodes: input,
291
+ dropped: []
292
+ };
293
+ const nodes = cloneNodes(input);
294
+ const labels = new Map(names.pages.map((page)=>[
295
+ page.id,
296
+ page.label
297
+ ]));
298
+ const dropped = [];
299
+ for (const node of Object.values(nodes)){
300
+ for (const slot of shownTexts(node)){
301
+ const before = slot.get();
302
+ const after = before.replace(PAGE_REF, (ref, id)=>{
303
+ var _labels_get;
304
+ return (_labels_get = labels.get(id)) != null ? _labels_get : ref;
305
+ });
306
+ if (after !== before) {
307
+ slot.set(after);
308
+ dropped.push(`"${before}" written as "${after}"`);
309
+ }
310
+ }
311
+ }
312
+ dropped.push(...pruneFound(nodes, rootId, refsOf(nodes, names)));
313
+ return {
314
+ nodes: nodes,
315
+ dropped
316
+ };
317
+ }
318
+ /**
319
+ * A language-built page's or frame's words held to both (AGL-3660): before
320
+ * the last answer, each gap and each internal reference is a violation that
321
+ * asks again; in the last, they are taken out and the rest is kept.
322
+ */ export function aiLayoutCopyCheck(input, rootId, names, last) {
323
+ if (!last) {
324
+ return {
325
+ nodes: input,
326
+ dropped: [],
327
+ violations: [
328
+ ...aiLayoutRefViolations(input, names),
329
+ ...aiLayoutGapViolations(input)
330
+ ]
331
+ };
332
+ }
333
+ const refs = aiLayoutWithoutRefs(input, rootId, names);
334
+ const gaps = aiLayoutWithoutGaps(refs.nodes, rootId);
335
+ return {
336
+ nodes: gaps.nodes,
337
+ dropped: [
338
+ ...refs.dropped,
339
+ ...gaps.dropped
340
+ ],
341
+ violations: []
342
+ };
343
+ }
344
+
345
+ //# sourceMappingURL=ai-layout-gaps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-gaps.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 type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiBracketedFacts, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { aiStoredScrollTargets } from '../runtime/ai-page-links'\n\n/**\n * No gap on a published page (AGL-3660). A guided start publishes what it\n * builds, so a fact the brief never gave — a phone number, an address, the\n * opening hours — is left out of a page or a frame built in the layout\n * language, never written as the bracketed gap (\"[phone number]\") a draft a\n * person edits first may carry. An answer holding one is asked again; the\n * last answer a generation takes has each one taken out instead: the line\n * that carries it, the row or card it leaves with nothing to say, and a\n * section left with only its heading.\n */\n\ninterface GapNode {\n componentId: string\n props?: Record<string, unknown>\n nodes?: string[]\n}\n\ntype GapNodes = Record<string, GapNode>\n\n/** Props that hold no words a visitor reads: an icon's drawing is path data. */\nconst NOT_WORDS = new Set(['iconPath', 'iconId'])\n\n/** The props a node's own words are in. */\nconst WORD_PROPS = ['children', 'primary', 'secondary', 'label'] as const\n\n/** Elements that say something with no words of their own. */\nconst SPEAKS_ALONE = new Set(['image', 'form', 'layoutSlot', 'reusableInstance', 'muiDrawerToggle', 'muiDrawer'])\n\nconst HEADING_VARIANTS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'overline'])\n\n/** The violation code a gap is named by. */\nexport const AI_LAYOUT_GAP_CODE = 'layout-gap'\n\n/** Every string a value holds, however deep, its icon drawings aside. */\nfunction stringsOf(value: unknown, key = ''): string[] {\n if (NOT_WORDS.has(key)) return []\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.flatMap((entry) => stringsOf(entry))\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>).flatMap(([name, entry]) => stringsOf(entry, name))\n }\n return []\n}\n\n/** Each node whose words carry a gap, with the gaps it carries. */\nfunction gapsOf(nodes: GapNodes): Array<{ id: string; facts: string[]; text: string }> {\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = stringsOf(node.props ?? {})\n const facts = aiBracketedFacts(texts)\n return facts.length ? [{ id, facts, text: texts.filter((text) => aiBracketedFacts([text]).length).join(' ') }] : []\n })\n}\n\n/** The gaps a language-built page or frame carries, as the violation that asks again. */\nexport function aiLayoutGapViolations(nodes: NodesMap): AiDoctrineViolation[] {\n const found = gapsOf(nodes as unknown as GapNodes)\n if (!found.length) return []\n const facts = aiBracketedFacts(found.flatMap((entry) => entry.facts))\n return [\n {\n rule: 14,\n code: AI_LAYOUT_GAP_CODE,\n message: `${facts.join(', ')} ${facts.length === 1 ? 'is a gap' : 'are gaps'} for a fact the brief does not give, and this site is published exactly as written. Leave each fact out: write the sentence without it, and make no list line, card or contact row for it. Never write a gap in square brackets, and never invent the fact.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\nfunction parentsOf(nodes: GapNodes): Map<string, string> {\n const parents = new Map<string, string>()\n for (const [id, node] of Object.entries(nodes)) for (const child of node.nodes ?? []) parents.set(child, id)\n return parents\n}\n\nfunction subtree(nodes: GapNodes, id: string): string[] {\n const ids: string[] = []\n const stack = [id]\n while (stack.length) {\n const next = stack.pop() as string\n if (!nodes[next] || ids.includes(next)) continue\n ids.push(next)\n stack.push(...(nodes[next].nodes ?? []))\n }\n return ids\n}\n\nfunction wordsOf(node: GapNode): string {\n for (const prop of WORD_PROPS) {\n const value = node.props?.[prop]\n if (typeof value === 'string' && value.trim()) return value.trim()\n }\n return ''\n}\n\nfunction isHeading(node: GapNode): boolean {\n if (node.componentId !== 'muiTypography') return false\n return HEADING_VARIANTS.has(String(node.props?.['variant'] ?? '')) || /^h[1-6]$/.test(String(node.props?.['component'] ?? ''))\n}\n\n/** Whether anything under a node says something; with `beyondHeadings`, anything but a heading. */\nfunction speaks(nodes: GapNodes, id: string, beyondHeadings = false): boolean {\n return subtree(nodes, id).some((each) => {\n const node = nodes[each]\n const says = SPEAKS_ALONE.has(node.componentId) || !!wordsOf(node)\n return says && !(beyondHeadings && isHeading(node))\n })\n}\n\n/** A node's props, its component's prop values copied too, so a rewrite never reaches the input. */\nfunction copyProps(props: Record<string, unknown>): Record<string, unknown> {\n const values = props['propValues']\n return values && typeof values === 'object' && !Array.isArray(values)\n ? { ...props, propValues: { ...(values as Record<string, unknown>) } }\n : { ...props }\n}\n\nfunction cloneNodes(input: NodesMap): GapNodes {\n return Object.fromEntries(\n Object.entries(input as unknown as GapNodes).map(([id, node]) => [\n id,\n {\n ...node,\n ...(node.props ? { props: copyProps(node.props) } : {}),\n ...(node.nodes ? { nodes: [...node.nodes] } : {}),\n },\n ]),\n )\n}\n\n/**\n * Takes each found node out, and each element above it left with nothing to\n * say, up to its section; a section other than the page's first left with\n * only its heading goes too, with any link that scrolled to it.\n */\nfunction pruneFound(nodes: GapNodes, rootId: string, found: ReadonlyArray<{ id: string; text: string }>): string[] {\n const dropped: string[] = []\n const sections = new Set(nodes[rootId]?.nodes ?? [])\n const touched = new Set<string>()\n const remove = (id: string) => {\n const parent = parentsOf(nodes).get(id)\n for (const each of subtree(nodes, id)) delete nodes[each]\n if (parent && nodes[parent]) nodes[parent].nodes = (nodes[parent].nodes ?? []).filter((child) => child !== id)\n }\n /** Takes a node out, and each element above it it leaves with nothing to say, short of a section. */\n const prune = (id: string) => {\n const parents = parentsOf(nodes)\n let section: string | undefined = id\n while (section && !sections.has(section)) section = parents.get(section)\n if (section && section !== id) touched.add(section)\n let parent = parents.get(id)\n remove(id)\n while (parent && parent !== rootId && nodes[parent]) {\n if (sections.has(parent) || speaks(nodes, parent)) break\n const above = parentsOf(nodes).get(parent)\n remove(parent)\n parent = above\n }\n }\n for (const entry of found) {\n if (!nodes[entry.id]) continue\n dropped.push(entry.text)\n prune(entry.id)\n }\n const first = nodes[rootId]?.nodes?.[0]\n for (const section of touched) {\n if (section === first || !nodes[section] || speaks(nodes, section, true)) continue\n const label = String(nodes[section].props?.['ariaLabel'] ?? section)\n remove(section)\n dropped.push(`the section \"${label}\", left with only its heading`)\n for (const [id, node] of Object.entries(nodes)) {\n if (nodes[id] && aiStoredScrollTargets(node).includes(section)) {\n dropped.push(`\"${wordsOf(node)}\", a link to that section`)\n prune(id)\n }\n }\n }\n return dropped\n}\n\n/** The page or frame with every gap taken out, and what was taken. */\nexport function aiLayoutWithoutGaps(input: NodesMap, rootId: string): { nodes: NodesMap; dropped: string[] } {\n const found = gapsOf(input as unknown as GapNodes)\n if (!found.length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const dropped = pruneFound(nodes, rootId, found)\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n// ── Internal references ─────────────────────────────────────────────────\n\n/**\n * What a link may name, which never belongs in words a visitor reads: the\n * site's pages, with the title a reference to one is written as, and every\n * other id the page or frame was built against (its forms, components and\n * sections).\n */\nexport interface AiLayoutRefNames {\n pages: ReadonlyArray<{ id: string; label: string }>\n ids: readonly string[]\n}\n\n/** The violation code an internal reference in copy is named by. */\nexport const AI_LAYOUT_REF_CODE = 'layout-internal-ref'\n\n/** A link's own notation, or a template token other than the site's own (`{{host.…}}`). */\nconst REF = /\\b(?:page|new|form|component|screen):[A-Za-z0-9_-]+|\\{\\{(?!\\s*host\\.)[^{}]*\\}\\}/g\nconst PAGE_REF = /\\bpage:([A-Za-z0-9_-]+)/g\n\n/** The props whose words a visitor reads, and a component's prop values. */\nconst SHOWN_PROPS = [...WORD_PROPS, 'alt', 'title']\n\nfunction shownTexts(node: GapNode): Array<{ get: () => string; set: (value: string) => void }> {\n const props = node.props ?? {}\n const slots: Array<{ get: () => string; set: (value: string) => void }> = []\n for (const prop of SHOWN_PROPS) {\n if (typeof props[prop] === 'string') slots.push({ get: () => props[prop] as string, set: (value) => (props[prop] = value) })\n }\n const values = props['propValues']\n if (values && typeof values === 'object' && !Array.isArray(values)) {\n const record = values as Record<string, unknown>\n for (const name of Object.keys(record)) {\n if (typeof record[name] === 'string') slots.push({ get: () => record[name] as string, set: (value) => (record[name] = value) })\n }\n }\n return slots\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction refsIn(text: string, names: AiLayoutRefNames): string[] {\n const found = (text.match(REF) ?? []).map((ref) => ref.trim())\n for (const id of names.ids) {\n if (id.length >= 6 && new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(id)}($|[^A-Za-z0-9_-])`).test(text)) found.push(id)\n }\n return [...new Set(found)]\n}\n\nfunction refsOf(nodes: GapNodes, names: AiLayoutRefNames): Array<{ id: string; refs: string[]; text: string }> {\n return Object.entries(nodes).flatMap(([id, node]) => {\n const texts = shownTexts(node).map((slot) => slot.get())\n const refs = [...new Set(texts.flatMap((text) => refsIn(text, names)))]\n return refs.length ? [{ id, refs, text: texts.filter((text) => refsIn(text, names).length).join(' ') }] : []\n })\n}\n\n/** Internal references in a language-built page's or frame's words, as the violation that asks again. */\nexport function aiLayoutRefViolations(nodes: NodesMap, names: AiLayoutRefNames): AiDoctrineViolation[] {\n const found = refsOf(nodes as unknown as GapNodes, names)\n if (!found.length) return []\n const refs = [...new Set(found.flatMap((entry) => entry.refs))].map((ref) => `\"${ref}\"`)\n return [\n {\n rule: 14,\n code: AI_LAYOUT_REF_CODE,\n message: `${refs.join(', ')} ${refs.length === 1 ? 'is a link reference' : 'are link references'} written into words a visitor reads. A reference goes only in a button's or a list item's to; in text, name the page by its title, or leave the reference out.`,\n nodeIds: found.map((entry) => entry.id),\n },\n ]\n}\n\n/**\n * The page or frame with no internal reference in its words: a page's\n * reference is written as that page's title, and a line that still names a\n * reference is taken out, as a gap is. Returns what was rewritten and taken.\n */\nexport function aiLayoutWithoutRefs(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n): { nodes: NodesMap; dropped: string[] } {\n if (!refsOf(input as unknown as GapNodes, names).length) return { nodes: input, dropped: [] }\n const nodes = cloneNodes(input)\n const labels = new Map(names.pages.map((page) => [page.id, page.label]))\n const dropped: string[] = []\n for (const node of Object.values(nodes)) {\n for (const slot of shownTexts(node)) {\n const before = slot.get()\n const after = before.replace(PAGE_REF, (ref, id: string) => labels.get(id) ?? ref)\n if (after !== before) {\n slot.set(after)\n dropped.push(`\"${before}\" written as \"${after}\"`)\n }\n }\n }\n dropped.push(...pruneFound(nodes, rootId, refsOf(nodes, names)))\n return { nodes: nodes as unknown as NodesMap, dropped }\n}\n\n/**\n * A language-built page's or frame's words held to both (AGL-3660): before\n * the last answer, each gap and each internal reference is a violation that\n * asks again; in the last, they are taken out and the rest is kept.\n */\nexport function aiLayoutCopyCheck(\n input: NodesMap,\n rootId: string,\n names: AiLayoutRefNames,\n last: boolean,\n): { nodes: NodesMap; dropped: string[]; violations: AiDoctrineViolation[] } {\n if (!last) {\n return { nodes: input, dropped: [], violations: [...aiLayoutRefViolations(input, names), ...aiLayoutGapViolations(input)] }\n }\n const refs = aiLayoutWithoutRefs(input, rootId, names)\n const gaps = aiLayoutWithoutGaps(refs.nodes, rootId)\n return { nodes: gaps.nodes, dropped: [...refs.dropped, ...gaps.dropped], violations: [] }\n}\n"],"names":["aiBracketedFacts","aiStoredScrollTargets","NOT_WORDS","Set","WORD_PROPS","SPEAKS_ALONE","HEADING_VARIANTS","AI_LAYOUT_GAP_CODE","stringsOf","value","key","has","Array","isArray","flatMap","entry","Object","entries","name","gapsOf","nodes","id","node","texts","props","facts","length","text","filter","join","aiLayoutGapViolations","found","rule","code","message","nodeIds","map","parentsOf","parents","Map","child","set","subtree","ids","stack","next","pop","includes","push","wordsOf","prop","trim","isHeading","componentId","String","test","speaks","beyondHeadings","some","each","says","copyProps","values","propValues","cloneNodes","input","fromEntries","pruneFound","rootId","dropped","sections","touched","remove","parent","get","prune","section","add","above","first","label","aiLayoutWithoutGaps","AI_LAYOUT_REF_CODE","REF","PAGE_REF","SHOWN_PROPS","shownTexts","slots","record","keys","escapeRegExp","replace","refsIn","names","match","ref","RegExp","refsOf","slot","refs","aiLayoutRefViolations","aiLayoutWithoutRefs","labels","pages","page","before","after","aiLayoutCopyCheck","last","violations","gaps"],"mappings":"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"}
@@ -0,0 +1,161 @@
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
+ import type { AiTool } from '../providers/contract';
18
+ /**
19
+ * The compact layout language (AGL-3660): what a model designs a page, and a
20
+ * site's header and footer, in — and the one place its shape is declared.
21
+ *
22
+ * ── Why a language, not a tree ───────────────────────────────────────────
23
+ *
24
+ * A page used to be asked for as a raw element tree, packed as escaped JSON
25
+ * text inside a tool call and held to the doctrine's building rules: Grid
26
+ * items sized as strings, every child attached, one h1, a destination on
27
+ * every button, headings under their ceiling. A real model kept about half
28
+ * of a page's sections on the first try, and a Free section was cut off at
29
+ * its token ceiling. The founder chose a language over a library of section
30
+ * templates (10/7): every page is designed for its own request, so no two
31
+ * sites come out alike, and the rules hold because CODE writes the tree.
32
+ *
33
+ * So the model says WHAT each section is and how it is arranged, in a few
34
+ * words of structure, and writes every word a visitor reads; the compiler
35
+ * (`ai-layout-compiler.ts`) turns that into the element tree with the
36
+ * palette's elements and the theme's tokens, by construction.
37
+ *
38
+ * ── The shape ────────────────────────────────────────────────────────────
39
+ *
40
+ * A page is its plan's sections, in order. A SECTION is
41
+ * - `band` — its background: `plain` (the page), `soft` (a paper band),
42
+ * `brand` (the brand color) or `dark` (always dark);
43
+ * - `cols` — a row of columns by relative width, as `[7, 5]` or `[1, 1, 1]`,
44
+ * up to four; absent, one column;
45
+ * - `align` — `start` or `center`;
46
+ * - `blocks` — what it shows, top to bottom.
47
+ *
48
+ * A BLOCK is one thing a visitor reads or uses. Its `kind` is one of
49
+ * `AI_LAYOUT_BLOCK_KINDS`; `col` places it in a column of the row (0 first),
50
+ * and a block with no `col` spans the section — above the row when it comes
51
+ * before the first placed block, below it when it comes after. `text` is its
52
+ * words, `to` where it goes, `style` its emphasis and `icon` a word from the
53
+ * icon library. A group — `list`, `cards`, `steps`, `stats`, `quotes`,
54
+ * `faq` — carries its `items`, each a `title`, a `text`, and optionally its
55
+ * own `to` and `icon`.
56
+ *
57
+ * Where a block goes (`to`): `page:<id>` for a page of this site (its label
58
+ * works too), `#<n>` for section n of this page, `form` for the site's form,
59
+ * a form's or a component's id for a `form` or `component` block, or an
60
+ * `https:` address the brief gave.
61
+ *
62
+ * A site's header and footer are two sections in the same language
63
+ * (`AI_LAYOUT_FRAME_TOOL`): the header's brand and navigation are the
64
+ * site's own and written by the compiler, so the model gives the header its
65
+ * band, its alignment and an optional call-to-action button; the footer is
66
+ * a section like any other.
67
+ *
68
+ * ── Cheap by shape ───────────────────────────────────────────────────────
69
+ *
70
+ * The schema is a structured object in the tool input (never JSON text in a
71
+ * string), small enough for a strict tool (`tool-schema-limits.spec.ts`), and
72
+ * the language's instructions ride in the cached prefix. A section is a few
73
+ * dozen words of structure beside its copy, so a whole page is one answer.
74
+ */
75
+ /** Every kind of block, in the order the schema lists them. */
76
+ export declare const AI_LAYOUT_BLOCK_KINDS: readonly ["eyebrow", "heading", "lede", "text", "note", "list", "button", "image", "cards", "steps", "stats", "quotes", "faq", "form", "component"];
77
+ export type AiLayoutBlockKind = (typeof AI_LAYOUT_BLOCK_KINDS)[number];
78
+ /** The kinds that carry `items`. */
79
+ export declare const AI_LAYOUT_GROUP_KINDS: ReadonlySet<AiLayoutBlockKind>;
80
+ export declare const AI_LAYOUT_BANDS: readonly ["plain", "soft", "brand", "dark"];
81
+ export type AiLayoutBand = (typeof AI_LAYOUT_BANDS)[number];
82
+ export declare const AI_LAYOUT_ALIGNS: readonly ["start", "center"];
83
+ export type AiLayoutAlign = (typeof AI_LAYOUT_ALIGNS)[number];
84
+ export declare const AI_LAYOUT_STYLES: readonly ["primary", "secondary", "quiet", "large"];
85
+ export type AiLayoutStyle = (typeof AI_LAYOUT_STYLES)[number];
86
+ /** The most columns a row holds; a fifth is folded into the fourth. */
87
+ export declare const AI_LAYOUT_MAX_COLS = 4;
88
+ /**
89
+ * The most items a group shows: one under the eight copies rule 8 reads as a
90
+ * list typed out by hand, which belongs in a dataset.
91
+ */
92
+ export declare const AI_LAYOUT_MAX_ITEMS = 7;
93
+ /** The most quotes a group shows: each is a gap the owner fills with a real one. */
94
+ export declare const AI_LAYOUT_MAX_QUOTES = 3;
95
+ export interface AiLayoutItem {
96
+ title: string;
97
+ text: string;
98
+ to?: string;
99
+ icon?: string;
100
+ }
101
+ export interface AiLayoutBlock {
102
+ kind: AiLayoutBlockKind;
103
+ col?: number;
104
+ text?: string;
105
+ to?: string;
106
+ style?: AiLayoutStyle;
107
+ icon?: string;
108
+ items?: AiLayoutItem[];
109
+ }
110
+ export interface AiLayoutSection {
111
+ band?: AiLayoutBand;
112
+ cols?: number[];
113
+ align?: AiLayoutAlign;
114
+ blocks: AiLayoutBlock[];
115
+ }
116
+ /** A page: its plan's sections, in order. */
117
+ export interface AiLayoutPageDoc {
118
+ sections: AiLayoutSection[];
119
+ }
120
+ /** A site's frame: the header and the footer every page renders between. */
121
+ export interface AiLayoutFrameDoc {
122
+ header: AiLayoutSection;
123
+ footer: AiLayoutSection;
124
+ }
125
+ /** The page tool: every section of one page, in plan order, in one answer. */
126
+ export declare const AI_LAYOUT_PAGE_TOOL: AiTool;
127
+ /** The frame tool: the site's header and footer, in the same language. */
128
+ export declare const AI_LAYOUT_FRAME_TOOL: AiTool;
129
+ /**
130
+ * The language as a model is taught it: the cached instructions both doors
131
+ * share. Every word of copy is the model's; this says only how a design is
132
+ * written down and what each word becomes.
133
+ */
134
+ export declare const AI_LAYOUT_LANGUAGE_TEXT: string;
135
+ /** What the reader settled on its own, for the job's record and the specs. */
136
+ export interface AiLayoutSettlement {
137
+ /** Where: `sections[2].blocks[1]`, `header`. */
138
+ at: string;
139
+ what: string;
140
+ }
141
+ /** A section read from an answer, or `null` with why it could not be. */
142
+ export declare function aiReadLayoutSection(raw: unknown, at: string, settled: AiLayoutSettlement[]): AiLayoutSection | null;
143
+ /** A page answer read: the sections it gave, by plan position, and the positions it could not. */
144
+ export interface AiLayoutPageReading {
145
+ /** One entry per planned section; `null` where the answer gave nothing usable. */
146
+ sections: Array<AiLayoutSection | null>;
147
+ settled: AiLayoutSettlement[];
148
+ }
149
+ /**
150
+ * The page an answer describes, against the number of sections its plan
151
+ * names: each planned section takes the answer's section at its position,
152
+ * an answer's extra sections are left out, and a planned section the answer
153
+ * left empty or out is `null` — the one thing a re-ask asks for again.
154
+ */
155
+ export declare function aiReadLayoutPage(raw: unknown, planned: number): AiLayoutPageReading;
156
+ /** A frame answer read: a header and a footer, either `null` where it was not usable. */
157
+ export declare function aiReadLayoutFrame(raw: unknown): {
158
+ header: AiLayoutSection | null;
159
+ footer: AiLayoutSection | null;
160
+ settled: AiLayoutSettlement[];
161
+ };