@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.
- package/package.json +17 -17
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +11 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +105 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +8 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +4 -0
- package/src/lib/jobs/ai-job-site-step.js +20 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +457 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +48 -6
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +22 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +19 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +5 -1
- package/src/lib/model/ai-site-build-progress.js +27 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +79 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +1 -1
- package/src/lib/providers/catalog.js +3 -3
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +12 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A layout at least as tall as the window (AGL-3596).
|
|
19
|
+
*
|
|
20
|
+
* A layout is header, page, footer, top to bottom, and on a page shorter than
|
|
21
|
+
* the window the footer used to end wherever the page did — on beta.230 a
|
|
22
|
+
* contact page left its footer floating halfway up the screen with blank page
|
|
23
|
+
* under it. So every layout the AI builds is written as one column at least
|
|
24
|
+
* the window's height, and the part of it that holds the Layout Slot takes
|
|
25
|
+
* the room left over: the footer sits at the bottom of a short page and
|
|
26
|
+
* follows the content of a long one.
|
|
27
|
+
*
|
|
28
|
+
* Written by the platform after the model answers and before the doctrine
|
|
29
|
+
* checks the tree, so the doctrine holds the column like anything else: it is
|
|
30
|
+
* a Stack with a style of its own, which no rule calls an empty wrapper.
|
|
31
|
+
*/
|
|
32
|
+
/** The id of the column the platform adds, when no node of the tree has it. */
|
|
33
|
+
export declare const AI_LAYOUT_FRAME_ID = "aiLayoutFrame";
|
|
34
|
+
/**
|
|
35
|
+
* The layout with its root's children in a column at least the window's
|
|
36
|
+
* height, and the child that holds the Layout Slot growing into the room
|
|
37
|
+
* left. A tree whose top already sets a minimum height is left as it came,
|
|
38
|
+
* and so is anything that is not a node map or holds no Layout Slot.
|
|
39
|
+
*/
|
|
40
|
+
export declare function aiLayoutWithFullHeight(tree: unknown): unknown;
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* A layout at least as tall as the window (AGL-3596).
|
|
19
|
+
*
|
|
20
|
+
* A layout is header, page, footer, top to bottom, and on a page shorter than
|
|
21
|
+
* the window the footer used to end wherever the page did — on beta.230 a
|
|
22
|
+
* contact page left its footer floating halfway up the screen with blank page
|
|
23
|
+
* under it. So every layout the AI builds is written as one column at least
|
|
24
|
+
* the window's height, and the part of it that holds the Layout Slot takes
|
|
25
|
+
* the room left over: the footer sits at the bottom of a short page and
|
|
26
|
+
* follows the content of a long one.
|
|
27
|
+
*
|
|
28
|
+
* Written by the platform after the model answers and before the doctrine
|
|
29
|
+
* checks the tree, so the doctrine holds the column like anything else: it is
|
|
30
|
+
* a Stack with a style of its own, which no rule calls an empty wrapper.
|
|
31
|
+
*/ /** The id of the column the platform adds, when no node of the tree has it. */ export const AI_LAYOUT_FRAME_ID = 'aiLayoutFrame';
|
|
32
|
+
const isRecord = (value)=>Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
33
|
+
const childrenOf = (node)=>Array.isArray(node == null ? void 0 : node.nodes) ? node.nodes.filter((id)=>typeof id === 'string') : [];
|
|
34
|
+
/**
|
|
35
|
+
* The layout with its root's children in a column at least the window's
|
|
36
|
+
* height, and the child that holds the Layout Slot growing into the room
|
|
37
|
+
* left. A tree whose top already sets a minimum height is left as it came,
|
|
38
|
+
* and so is anything that is not a node map or holds no Layout Slot.
|
|
39
|
+
*/ export function aiLayoutWithFullHeight(tree) {
|
|
40
|
+
var _source_grower_sx;
|
|
41
|
+
var _source_top__sx;
|
|
42
|
+
if (!isRecord(tree) || !isRecord(tree['nodes'])) return tree;
|
|
43
|
+
const source = tree['nodes'];
|
|
44
|
+
const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null;
|
|
45
|
+
if (!rootId || !isRecord(source[rootId])) return tree;
|
|
46
|
+
const top = childrenOf(source[rootId]).filter((id)=>isRecord(source[id]));
|
|
47
|
+
if (!top.length) return tree;
|
|
48
|
+
if (top.length === 1 && ((_source_top__sx = source[top[0]].sx) == null ? void 0 : _source_top__sx['minHeight']) !== undefined) return tree;
|
|
49
|
+
const holdsSlot = (id, seen = new Set())=>{
|
|
50
|
+
if (seen.has(id) || !isRecord(source[id])) return false;
|
|
51
|
+
seen.add(id);
|
|
52
|
+
return source[id].componentId === 'layoutSlot' || childrenOf(source[id]).some((child)=>holdsSlot(child, seen));
|
|
53
|
+
};
|
|
54
|
+
const grower = top.find((id)=>holdsSlot(id));
|
|
55
|
+
if (!grower) return tree;
|
|
56
|
+
let frameId = AI_LAYOUT_FRAME_ID;
|
|
57
|
+
for(let n = 2; frameId in source; n += 1)frameId = `${AI_LAYOUT_FRAME_ID}${n}`;
|
|
58
|
+
const nodes = _extends({}, source, {
|
|
59
|
+
[rootId]: _extends({}, source[rootId], {
|
|
60
|
+
nodes: [
|
|
61
|
+
frameId
|
|
62
|
+
]
|
|
63
|
+
}),
|
|
64
|
+
[frameId]: {
|
|
65
|
+
componentId: 'muiStack',
|
|
66
|
+
sx: {
|
|
67
|
+
minHeight: '100vh'
|
|
68
|
+
},
|
|
69
|
+
nodes: top
|
|
70
|
+
},
|
|
71
|
+
[grower]: _extends({}, source[grower], {
|
|
72
|
+
sx: _extends({}, (_source_grower_sx = source[grower].sx) != null ? _source_grower_sx : {}, {
|
|
73
|
+
flexGrow: 1
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
return _extends({}, tree, {
|
|
78
|
+
nodes
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=ai-layout-full-height.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-full-height.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A layout at least as tall as the window (AGL-3596).\n *\n * A layout is header, page, footer, top to bottom, and on a page shorter than\n * the window the footer used to end wherever the page did — on beta.230 a\n * contact page left its footer floating halfway up the screen with blank page\n * under it. So every layout the AI builds is written as one column at least\n * the window's height, and the part of it that holds the Layout Slot takes\n * the room left over: the footer sits at the bottom of a short page and\n * follows the content of a long one.\n *\n * Written by the platform after the model answers and before the doctrine\n * checks the tree, so the doctrine holds the column like anything else: it is\n * a Stack with a style of its own, which no rule calls an empty wrapper.\n */\n\n/** The id of the column the platform adds, when no node of the tree has it. */\nexport const AI_LAYOUT_FRAME_ID = 'aiLayoutFrame'\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/**\n * The layout with its root's children in a column at least the window's\n * height, and the child that holds the Layout Slot growing into the room\n * left. A tree whose top already sets a minimum height is left as it came,\n * and so is anything that is not a node map or holds no Layout Slot.\n */\nexport function aiLayoutWithFullHeight(tree: unknown): unknown {\n if (!isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const source = tree['nodes'] as Record<string, RawNode>\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n if (!rootId || !isRecord(source[rootId])) return tree\n const top = childrenOf(source[rootId]).filter((id) => isRecord(source[id]))\n if (!top.length) return tree\n if (top.length === 1 && source[top[0]].sx?.['minHeight'] !== undefined) return tree\n\n const holdsSlot = (id: string, seen = new Set<string>()): boolean => {\n if (seen.has(id) || !isRecord(source[id])) return false\n seen.add(id)\n return source[id].componentId === 'layoutSlot' || childrenOf(source[id]).some((child) => holdsSlot(child, seen))\n }\n const grower = top.find((id) => holdsSlot(id))\n if (!grower) return tree\n\n let frameId = AI_LAYOUT_FRAME_ID\n for (let n = 2; frameId in source; n += 1) frameId = `${AI_LAYOUT_FRAME_ID}${n}`\n const nodes: Record<string, RawNode> = {\n ...source,\n [rootId]: { ...source[rootId], nodes: [frameId] },\n [frameId]: { componentId: 'muiStack', sx: { minHeight: '100vh' }, nodes: top },\n [grower]: { ...source[grower], sx: { ...(source[grower].sx ?? {}), flexGrow: 1 } },\n }\n return { ...tree, nodes }\n}\n"],"names":["AI_LAYOUT_FRAME_ID","isRecord","value","Boolean","Array","isArray","childrenOf","node","nodes","filter","id","aiLayoutWithFullHeight","tree","source","rootId","top","length","sx","undefined","holdsSlot","seen","Set","has","add","componentId","some","child","grower","find","frameId","n","minHeight","flexGrow"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GAED,6EAA6E,GAC7E,OAAO,MAAMA,qBAAqB,gBAAe;AAIjD,MAAMC,WAAW,CAACC,QAChBC,QAAQD,UAAU,OAAOA,UAAU,YAAY,CAACE,MAAMC,OAAO,CAACH;AAEhE,MAAMI,aAAa,CAACC,OAClBH,MAAMC,OAAO,CAACE,wBAAAA,KAAMC,KAAK,IAAI,AAACD,KAAKC,KAAK,CAAeC,MAAM,CAAC,CAACC,KAAqB,OAAOA,OAAO,YAAY,EAAE;AAElH;;;;;CAKC,GACD,OAAO,SAASC,uBAAuBC,IAAa;QAuBPC;QAhBnBA;IANxB,IAAI,CAACZ,SAASW,SAAS,CAACX,SAASW,IAAI,CAAC,QAAQ,GAAG,OAAOA;IACxD,MAAMC,SAASD,IAAI,CAAC,QAAQ;IAC5B,MAAME,SAAS,OAAOF,IAAI,CAAC,SAAS,KAAK,WAAWA,IAAI,CAAC,SAAS,GAAG;IACrE,IAAI,CAACE,UAAU,CAACb,SAASY,MAAM,CAACC,OAAO,GAAG,OAAOF;IACjD,MAAMG,MAAMT,WAAWO,MAAM,CAACC,OAAO,EAAEL,MAAM,CAAC,CAACC,KAAOT,SAASY,MAAM,CAACH,GAAG;IACzE,IAAI,CAACK,IAAIC,MAAM,EAAE,OAAOJ;IACxB,IAAIG,IAAIC,MAAM,KAAK,KAAKH,EAAAA,kBAAAA,MAAM,CAACE,GAAG,CAAC,EAAE,CAAC,CAACE,EAAE,qBAAjBJ,eAAmB,CAAC,YAAY,MAAKK,WAAW,OAAON;IAE/E,MAAMO,YAAY,CAACT,IAAYU,OAAO,IAAIC,KAAa;QACrD,IAAID,KAAKE,GAAG,CAACZ,OAAO,CAACT,SAASY,MAAM,CAACH,GAAG,GAAG,OAAO;QAClDU,KAAKG,GAAG,CAACb;QACT,OAAOG,MAAM,CAACH,GAAG,CAACc,WAAW,KAAK,gBAAgBlB,WAAWO,MAAM,CAACH,GAAG,EAAEe,IAAI,CAAC,CAACC,QAAUP,UAAUO,OAAON;IAC5G;IACA,MAAMO,SAASZ,IAAIa,IAAI,CAAC,CAAClB,KAAOS,UAAUT;IAC1C,IAAI,CAACiB,QAAQ,OAAOf;IAEpB,IAAIiB,UAAU7B;IACd,IAAK,IAAI8B,IAAI,GAAGD,WAAWhB,QAAQiB,KAAK,EAAGD,UAAU,GAAG7B,qBAAqB8B,GAAG;IAChF,MAAMtB,QAAiC,aAClCK;QACH,CAACC,OAAO,EAAE,aAAKD,MAAM,CAACC,OAAO;YAAEN,OAAO;gBAACqB;aAAQ;;QAC/C,CAACA,QAAQ,EAAE;YAAEL,aAAa;YAAYP,IAAI;gBAAEc,WAAW;YAAQ;YAAGvB,OAAOO;QAAI;QAC7E,CAACY,OAAO,EAAE,aAAKd,MAAM,CAACc,OAAO;YAAEV,IAAI,cAAMJ,oBAAAA,MAAM,CAACc,OAAO,CAACV,EAAE,YAAjBJ,oBAAqB,CAAC;gBAAImB,UAAU;;;;IAE/E,OAAO,aAAKpB;QAAMJ;;AACpB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { AiDoctrineTree, AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
18
|
+
/**
|
|
19
|
+
* What a layout may say about the business it frames (AGL-3596).
|
|
20
|
+
*
|
|
21
|
+
* A layout's header and footer are on every page, so a fact in them is
|
|
22
|
+
* published everywhere at once. Two went wrong on beta.230: the model coined
|
|
23
|
+
* a name for the business ("Austin Paws Grooming" for a site called Hillside
|
|
24
|
+
* Dog Grooming), and a footer is exactly where a model reaches for a phone
|
|
25
|
+
* number, an email or opening hours the brief never gave.
|
|
26
|
+
*
|
|
27
|
+
* - The site's name is the brand, and it is written as the host token that
|
|
28
|
+
* reads it (`{{host.businessName}}`), so a rename in Setup follows on every
|
|
29
|
+
* page rather than leaving the name it was built under
|
|
30
|
+
* (`aiLayoutWithSiteName`).
|
|
31
|
+
* - A contact detail the brief does not give is refused by the layout's own
|
|
32
|
+
* check (`aiLayoutInventedContactViolations`), so the model is asked again
|
|
33
|
+
* to leave it out: an email, a phone number, a street address or opening
|
|
34
|
+
* hours appear in the layout only when the brief has them.
|
|
35
|
+
*/
|
|
36
|
+
/** The host token that reads a site's own name at render. */
|
|
37
|
+
export declare const AI_SITE_NAME_TOKEN = "{{host.businessName}}";
|
|
38
|
+
/**
|
|
39
|
+
* The tree with every written occurrence of the site's name — the brand, the
|
|
40
|
+
* copyright line — replaced by the host token that reads it. Matched whole,
|
|
41
|
+
* ignoring case and runs of spaces. Anything that is not a node map, and a
|
|
42
|
+
* name too short to match safely, leaves the tree as it came.
|
|
43
|
+
*/
|
|
44
|
+
export declare function aiLayoutWithSiteName(tree: unknown, siteName: string | null | undefined): unknown;
|
|
45
|
+
/**
|
|
46
|
+
* Rule 14 for a layout: every email address, phone number, street address
|
|
47
|
+
* and opening time its words carry is one the brief gives. A number is read
|
|
48
|
+
* by its digits, so a brief's "512.555.0142" admits "(512) 555-0142"; a year
|
|
49
|
+
* or a price is not a phone number, so only a run of seven digits or more is
|
|
50
|
+
* read as one. An address is matched by its house number and first street
|
|
51
|
+
* word; hours are admitted when the brief gives any, since a brief that names
|
|
52
|
+
* its hours is spelled a dozen ways a footer may fairly reword.
|
|
53
|
+
*/
|
|
54
|
+
export declare function aiLayoutInventedContactViolations(tree: AiDoctrineTree, brief: string): AiDoctrineViolation[];
|
|
@@ -0,0 +1,153 @@
|
|
|
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 { walkTree } from "../runtime/ai-doctrine-validators.js";
|
|
18
|
+
/**
|
|
19
|
+
* What a layout may say about the business it frames (AGL-3596).
|
|
20
|
+
*
|
|
21
|
+
* A layout's header and footer are on every page, so a fact in them is
|
|
22
|
+
* published everywhere at once. Two went wrong on beta.230: the model coined
|
|
23
|
+
* a name for the business ("Austin Paws Grooming" for a site called Hillside
|
|
24
|
+
* Dog Grooming), and a footer is exactly where a model reaches for a phone
|
|
25
|
+
* number, an email or opening hours the brief never gave.
|
|
26
|
+
*
|
|
27
|
+
* - The site's name is the brand, and it is written as the host token that
|
|
28
|
+
* reads it (`{{host.businessName}}`), so a rename in Setup follows on every
|
|
29
|
+
* page rather than leaving the name it was built under
|
|
30
|
+
* (`aiLayoutWithSiteName`).
|
|
31
|
+
* - A contact detail the brief does not give is refused by the layout's own
|
|
32
|
+
* check (`aiLayoutInventedContactViolations`), so the model is asked again
|
|
33
|
+
* to leave it out: an email, a phone number, a street address or opening
|
|
34
|
+
* hours appear in the layout only when the brief has them.
|
|
35
|
+
*/ /** The host token that reads a site's own name at render. */ export const AI_SITE_NAME_TOKEN = '{{host.businessName}}';
|
|
36
|
+
const isRecord = (value)=>Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
37
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
38
|
+
/**
|
|
39
|
+
* The tree with every written occurrence of the site's name — the brand, the
|
|
40
|
+
* copyright line — replaced by the host token that reads it. Matched whole,
|
|
41
|
+
* ignoring case and runs of spaces. Anything that is not a node map, and a
|
|
42
|
+
* name too short to match safely, leaves the tree as it came.
|
|
43
|
+
*/ export function aiLayoutWithSiteName(tree, siteName) {
|
|
44
|
+
const name = (siteName != null ? siteName : '').replace(/\s+/g, ' ').trim();
|
|
45
|
+
if (name.length < 3 || !isRecord(tree) || !isRecord(tree['nodes'])) return tree;
|
|
46
|
+
const pattern = new RegExp(`(?<![\\p{L}\\p{N}])${escapeRegExp(name).replace(/ /g, '\\s+')}(?![\\p{L}\\p{N}])`, 'giu');
|
|
47
|
+
let changed = false;
|
|
48
|
+
const nodes = {};
|
|
49
|
+
for (const [id, node] of Object.entries(tree['nodes'])){
|
|
50
|
+
if (!isRecord(node) || !isRecord(node['props'])) {
|
|
51
|
+
nodes[id] = node;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const props = {};
|
|
55
|
+
let touched = false;
|
|
56
|
+
for (const [key, value] of Object.entries(node['props'])){
|
|
57
|
+
const next = typeof value === 'string' ? value.replace(pattern, AI_SITE_NAME_TOKEN) : value;
|
|
58
|
+
if (next !== value) touched = true;
|
|
59
|
+
props[key] = next;
|
|
60
|
+
}
|
|
61
|
+
nodes[id] = touched ? _extends({}, node, {
|
|
62
|
+
props
|
|
63
|
+
}) : node;
|
|
64
|
+
changed || (changed = touched);
|
|
65
|
+
}
|
|
66
|
+
return changed ? _extends({}, tree, {
|
|
67
|
+
nodes
|
|
68
|
+
}) : tree;
|
|
69
|
+
}
|
|
70
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
71
|
+
/** A phone number as people write one: seven or more digits, with the usual separators between them. */ const PHONE = /(?:\+?\(?\d[\d\s().-]{5,}\d)/g;
|
|
72
|
+
const digitsOf = (value)=>value.replace(/\D/g, '');
|
|
73
|
+
const STREET_SUFFIX = 'Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Way|Court|Ct|Place|Pl|Parkway|Pkwy|Highway|Hwy|Trail|Trl|Circle|Cir|Square|Sq';
|
|
74
|
+
/** A street address as a footer writes one: a house number, one to four words, and a street suffix. */ const ADDRESS = new RegExp(`\\b\\d{1,6}\\s+(?:[A-Z0-9][\\w'.-]*\\s+){1,4}(?:${STREET_SUFFIX})\\b\\.?`, 'g');
|
|
75
|
+
const DAY = '(?:Mon(?:day)?|Tue(?:s|sday)?|Wed(?:nesday)?|Thu(?:r|rs|rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)';
|
|
76
|
+
/**
|
|
77
|
+
* Opening hours: a time of day ("9am", "8:30 PM") or a range of days
|
|
78
|
+
* ("Mon–Fri", "Monday through Saturday"). A bare "9–5" is left alone, since
|
|
79
|
+
* "2–3 weeks" is written the same way.
|
|
80
|
+
*/ const HOURS = new RegExp([
|
|
81
|
+
'\\b\\d{1,2}(?::\\d{2})?\\s?(?:[ap]\\.?m\\.?)(?![a-z])',
|
|
82
|
+
`\\b${DAY}\\s?(?:[–—-]|to|through|thru)\\s?${DAY}\\b`
|
|
83
|
+
].join('|'), 'gi');
|
|
84
|
+
/** SVG path data: a move command, then only commands and numbers. */ const SVG_PATH_DATA = /^\s*[Mm][\s\d.,eE+-]*[\d.][\s\d.,eE+\-MmLlHhVvCcSsQqTtAaZz]*$/;
|
|
85
|
+
/** The words of an address, as a brief and a layout can both spell them. */ const addressKey = (value)=>{
|
|
86
|
+
var _value_toLowerCase_match;
|
|
87
|
+
const [number, word] = (_value_toLowerCase_match = value.toLowerCase().match(/[a-z0-9]+/g)) != null ? _value_toLowerCase_match : [];
|
|
88
|
+
return `${number != null ? number : ''} ${word != null ? word : ''}`;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The contact details a brief gives: its email addresses, the digits of its
|
|
92
|
+
* phone numbers, the house number and first word of its street addresses,
|
|
93
|
+
* and whether it gives opening hours at all.
|
|
94
|
+
*/ function briefFacts(brief) {
|
|
95
|
+
var _brief_match, _brief_match1;
|
|
96
|
+
return {
|
|
97
|
+
emails: new Set(((_brief_match = brief.match(EMAIL)) != null ? _brief_match : []).map((email)=>email.toLowerCase())),
|
|
98
|
+
digits: digitsOf(brief),
|
|
99
|
+
addresses: new Set(((_brief_match1 = brief.match(ADDRESS)) != null ? _brief_match1 : []).map(addressKey)),
|
|
100
|
+
hours: new RegExp(HOURS.source, 'i').test(brief)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Rule 14 for a layout: every email address, phone number, street address
|
|
105
|
+
* and opening time its words carry is one the brief gives. A number is read
|
|
106
|
+
* by its digits, so a brief's "512.555.0142" admits "(512) 555-0142"; a year
|
|
107
|
+
* or a price is not a phone number, so only a run of seven digits or more is
|
|
108
|
+
* read as one. An address is matched by its house number and first street
|
|
109
|
+
* word; hours are admitted when the brief gives any, since a brief that names
|
|
110
|
+
* its hours is spelled a dozen ways a footer may fairly reword.
|
|
111
|
+
*/ export function aiLayoutInventedContactViolations(tree, brief) {
|
|
112
|
+
const given = briefFacts(brief);
|
|
113
|
+
const invented = [];
|
|
114
|
+
const nodeIds = [];
|
|
115
|
+
for (const { id, node } of walkTree(tree)){
|
|
116
|
+
var _node_props;
|
|
117
|
+
for (const value of Object.values((_node_props = node.props) != null ? _node_props : {})){
|
|
118
|
+
// An icon's drawing (`iconPath`) is SVG path data, whose coordinates read as phone numbers.
|
|
119
|
+
if (typeof value !== 'string' || SVG_PATH_DATA.test(value)) continue;
|
|
120
|
+
const matches = (pattern)=>{
|
|
121
|
+
var _value_match;
|
|
122
|
+
return [
|
|
123
|
+
...(_value_match = value.match(pattern)) != null ? _value_match : []
|
|
124
|
+
];
|
|
125
|
+
};
|
|
126
|
+
const found = [
|
|
127
|
+
...matches(EMAIL).filter((email)=>!given.emails.has(email.toLowerCase())),
|
|
128
|
+
...matches(PHONE).filter((phone)=>{
|
|
129
|
+
const digits = digitsOf(phone);
|
|
130
|
+
return digits.length >= 7 && !given.digits.includes(digits);
|
|
131
|
+
}),
|
|
132
|
+
...matches(ADDRESS).filter((address)=>!given.addresses.has(addressKey(address))),
|
|
133
|
+
...given.hours ? [] : matches(HOURS)
|
|
134
|
+
];
|
|
135
|
+
if (!found.length) continue;
|
|
136
|
+
invented.push(...found.map((fact)=>fact.trim()));
|
|
137
|
+
if (!nodeIds.includes(id)) nodeIds.push(id);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!invented.length) return [];
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
rule: 14,
|
|
144
|
+
code: 'contact-not-in-brief',
|
|
145
|
+
message: `${[
|
|
146
|
+
...new Set(invented)
|
|
147
|
+
].map((fact)=>`"${fact}"`).join(', ')} is not in the brief. A layout shows only the contact details the brief gives: leave this out, and never invent one.`,
|
|
148
|
+
nodeIds
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=ai-layout-site-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-site-facts.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 { AiDoctrineNode, AiDoctrineTree, AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport { walkTree } from '../runtime/ai-doctrine-validators'\n\n/**\n * What a layout may say about the business it frames (AGL-3596).\n *\n * A layout's header and footer are on every page, so a fact in them is\n * published everywhere at once. Two went wrong on beta.230: the model coined\n * a name for the business (\"Austin Paws Grooming\" for a site called Hillside\n * Dog Grooming), and a footer is exactly where a model reaches for a phone\n * number, an email or opening hours the brief never gave.\n *\n * - The site's name is the brand, and it is written as the host token that\n * reads it (`{{host.businessName}}`), so a rename in Setup follows on every\n * page rather than leaving the name it was built under\n * (`aiLayoutWithSiteName`).\n * - A contact detail the brief does not give is refused by the layout's own\n * check (`aiLayoutInventedContactViolations`), so the model is asked again\n * to leave it out: an email, a phone number, a street address or opening\n * hours appear in the layout only when the brief has them.\n */\n\n/** The host token that reads a site's own name at render. */\nexport const AI_SITE_NAME_TOKEN = '{{host.businessName}}'\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * The tree with every written occurrence of the site's name — the brand, the\n * copyright line — replaced by the host token that reads it. Matched whole,\n * ignoring case and runs of spaces. Anything that is not a node map, and a\n * name too short to match safely, leaves the tree as it came.\n */\nexport function aiLayoutWithSiteName(tree: unknown, siteName: string | null | undefined): unknown {\n const name = (siteName ?? '').replace(/\\s+/g, ' ').trim()\n if (name.length < 3 || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const pattern = new RegExp(`(?<![\\\\p{L}\\\\p{N}])${escapeRegExp(name).replace(/ /g, '\\\\s+')}(?![\\\\p{L}\\\\p{N}])`, 'giu')\n let changed = false\n const nodes: Record<string, unknown> = {}\n for (const [id, node] of Object.entries(tree['nodes'])) {\n if (!isRecord(node) || !isRecord(node['props'])) {\n nodes[id] = node\n continue\n }\n const props: Record<string, unknown> = {}\n let touched = false\n for (const [key, value] of Object.entries(node['props'])) {\n const next = typeof value === 'string' ? value.replace(pattern, AI_SITE_NAME_TOKEN) : value\n if (next !== value) touched = true\n props[key] = next\n }\n nodes[id] = touched ? { ...node, props } : node\n changed ||= touched\n }\n return changed ? { ...tree, nodes } : tree\n}\n\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g\n/** A phone number as people write one: seven or more digits, with the usual separators between them. */\nconst PHONE = /(?:\\+?\\(?\\d[\\d\\s().-]{5,}\\d)/g\n\nconst digitsOf = (value: string) => value.replace(/\\D/g, '')\n\nconst STREET_SUFFIX =\n 'Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Way|Court|Ct|Place|Pl|Parkway|Pkwy|Highway|Hwy|Trail|Trl|Circle|Cir|Square|Sq'\n/** A street address as a footer writes one: a house number, one to four words, and a street suffix. */\nconst ADDRESS = new RegExp(`\\\\b\\\\d{1,6}\\\\s+(?:[A-Z0-9][\\\\w'.-]*\\\\s+){1,4}(?:${STREET_SUFFIX})\\\\b\\\\.?`, 'g')\nconst DAY = '(?:Mon(?:day)?|Tue(?:s|sday)?|Wed(?:nesday)?|Thu(?:r|rs|rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)'\n/**\n * Opening hours: a time of day (\"9am\", \"8:30 PM\") or a range of days\n * (\"Mon–Fri\", \"Monday through Saturday\"). A bare \"9–5\" is left alone, since\n * \"2–3 weeks\" is written the same way.\n */\nconst HOURS = new RegExp(\n [\n '\\\\b\\\\d{1,2}(?::\\\\d{2})?\\\\s?(?:[ap]\\\\.?m\\\\.?)(?![a-z])',\n `\\\\b${DAY}\\\\s?(?:[–—-]|to|through|thru)\\\\s?${DAY}\\\\b`,\n ].join('|'),\n 'gi',\n)\n\n/** SVG path data: a move command, then only commands and numbers. */\nconst SVG_PATH_DATA = /^\\s*[Mm][\\s\\d.,eE+-]*[\\d.][\\s\\d.,eE+\\-MmLlHhVvCcSsQqTtAaZz]*$/\n\n/** The words of an address, as a brief and a layout can both spell them. */\nconst addressKey = (value: string) => {\n const [number, word] = value.toLowerCase().match(/[a-z0-9]+/g) ?? []\n return `${number ?? ''} ${word ?? ''}`\n}\n\n/**\n * The contact details a brief gives: its email addresses, the digits of its\n * phone numbers, the house number and first word of its street addresses,\n * and whether it gives opening hours at all.\n */\nfunction briefFacts(brief: string): { emails: Set<string>; digits: string; addresses: Set<string>; hours: boolean } {\n return {\n emails: new Set((brief.match(EMAIL) ?? []).map((email) => email.toLowerCase())),\n digits: digitsOf(brief),\n addresses: new Set((brief.match(ADDRESS) ?? []).map(addressKey)),\n hours: new RegExp(HOURS.source, 'i').test(brief),\n }\n}\n\n/**\n * Rule 14 for a layout: every email address, phone number, street address\n * and opening time its words carry is one the brief gives. A number is read\n * by its digits, so a brief's \"512.555.0142\" admits \"(512) 555-0142\"; a year\n * or a price is not a phone number, so only a run of seven digits or more is\n * read as one. An address is matched by its house number and first street\n * word; hours are admitted when the brief gives any, since a brief that names\n * its hours is spelled a dozen ways a footer may fairly reword.\n */\nexport function aiLayoutInventedContactViolations(tree: AiDoctrineTree, brief: string): AiDoctrineViolation[] {\n const given = briefFacts(brief)\n const invented: string[] = []\n const nodeIds: string[] = []\n for (const { id, node } of walkTree(tree)) {\n for (const value of Object.values((node as AiDoctrineNode).props ?? {})) {\n // An icon's drawing (`iconPath`) is SVG path data, whose coordinates read as phone numbers.\n if (typeof value !== 'string' || SVG_PATH_DATA.test(value)) continue\n const matches = (pattern: RegExp): string[] => [...(value.match(pattern) ?? [])]\n const found: string[] = [\n ...matches(EMAIL).filter((email) => !given.emails.has(email.toLowerCase())),\n ...matches(PHONE).filter((phone) => {\n const digits = digitsOf(phone)\n return digits.length >= 7 && !given.digits.includes(digits)\n }),\n ...matches(ADDRESS).filter((address) => !given.addresses.has(addressKey(address))),\n ...(given.hours ? [] : matches(HOURS)),\n ]\n if (!found.length) continue\n invented.push(...found.map((fact) => fact.trim()))\n if (!nodeIds.includes(id)) nodeIds.push(id)\n }\n }\n if (!invented.length) return []\n return [\n {\n rule: 14,\n code: 'contact-not-in-brief',\n message: `${[...new Set(invented)].map((fact) => `\"${fact}\"`).join(', ')} is not in the brief. A layout shows only the contact details the brief gives: leave this out, and never invent one.`,\n nodeIds,\n },\n ]\n}\n"],"names":["walkTree","AI_SITE_NAME_TOKEN","isRecord","value","Boolean","Array","isArray","escapeRegExp","replace","aiLayoutWithSiteName","tree","siteName","name","trim","length","pattern","RegExp","changed","nodes","id","node","Object","entries","props","touched","key","next","EMAIL","PHONE","digitsOf","STREET_SUFFIX","ADDRESS","DAY","HOURS","join","SVG_PATH_DATA","addressKey","number","word","toLowerCase","match","briefFacts","brief","emails","Set","map","email","digits","addresses","hours","source","test","aiLayoutInventedContactViolations","given","invented","nodeIds","values","matches","found","filter","has","phone","includes","address","push","fact","rule","code","message"],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
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 AiIconLibraryEntry } from '../runtime/ai-icon-library';
|
|
18
|
+
import { type AiLayoutBand, type AiLayoutBlock, type AiLayoutSection, type AiLayoutSettlement } from './ai-layout-language';
|
|
19
|
+
import { type AiLayoutDestination, type AiLayoutLinkScope, type AiLayoutTargets } from './ai-layout-links';
|
|
20
|
+
import { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree';
|
|
21
|
+
/**
|
|
22
|
+
* The layout language compiler (AGL-3660): a page designed in the language
|
|
23
|
+
* (`ai-layout-language.ts`) becomes the element tree the page stores — built
|
|
24
|
+
* from the palette's elements and the theme's tokens only, so the doctrine's
|
|
25
|
+
* building rules hold by construction rather than by a re-ask.
|
|
26
|
+
*
|
|
27
|
+
* What the compiler decides, so no model has to:
|
|
28
|
+
*
|
|
29
|
+
* - STRUCTURE. A section is a semantic `section` band around a Container at
|
|
30
|
+
* a stock width, its content a Stack on the spacing scale; a row of
|
|
31
|
+
* columns is a Grid container whose items are sized `xs:12 md:N` from the
|
|
32
|
+
* relative widths, full width on a phone; a group of cards is a Grid of
|
|
33
|
+
* sized items, or a Stack where it would be one column.
|
|
34
|
+
* - THE OUTLINE. The first section's first heading is the page's one h1 (the
|
|
35
|
+
* plan's title stands in where it gave none); every other section opens at
|
|
36
|
+
* h2; a card's title is one level under its section's heading.
|
|
37
|
+
* - TOKENS. Colors are palette roles, never values: a `brand` band is
|
|
38
|
+
* `primary.main` with its contrast text, a `dark` band pins the site's dark
|
|
39
|
+
* scheme, a `soft` band is the paper surface. So a band reads in the
|
|
40
|
+
* visitor's light or dark mode alike.
|
|
41
|
+
* - LINKS. A button goes to a real page id, a section of this page through
|
|
42
|
+
* the platform's Scroll to element interaction, or an address the brief
|
|
43
|
+
* gave (`ai-layout-links.ts`); one with nowhere to go is left out.
|
|
44
|
+
* - THE PLAN. A form or a component the plan places in a section is placed
|
|
45
|
+
* there, by its id, whether or not the answer remembered it.
|
|
46
|
+
* - RULE 1. Where the workspace keeps reusable components, a section's
|
|
47
|
+
* repeated items are instances of the component its plan places; with
|
|
48
|
+
* none to place they are drawn in their compact form, a title over its
|
|
49
|
+
* text, which is smaller than a block worth a component. A workspace that
|
|
50
|
+
* keeps none (Free) draws its repeats in full.
|
|
51
|
+
* - RULES 8, 9, 14, 16. Seven items a group at most; every picture carries
|
|
52
|
+
* its description as alt text; copy fitted to its place at a clean
|
|
53
|
+
* boundary, with any phone number or email the brief did not give written
|
|
54
|
+
* as a gap; no wrapper around a lone wrapper, no empty element, and no
|
|
55
|
+
* multi-key inline style repeated across a page.
|
|
56
|
+
*/
|
|
57
|
+
export interface AiLayoutPagePlan {
|
|
58
|
+
/** The page's title in its plan: the h1 where the first section names none. */
|
|
59
|
+
title: string;
|
|
60
|
+
sections: ReadonlyArray<{
|
|
61
|
+
name: string;
|
|
62
|
+
uses: readonly string[];
|
|
63
|
+
items: number;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
export interface AiLayoutCompileOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Whether the workspace keeps reusable components (AGL-3030). Where it does,
|
|
69
|
+
* rule 1 holds: repeated items are instances of a component the plan
|
|
70
|
+
* places, or drawn in their compact form.
|
|
71
|
+
*/
|
|
72
|
+
reusableComponents: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The ids the page stores its sections under, in plan order
|
|
75
|
+
* (`aiPageSectionNodeId`): each section root is written under its own, and
|
|
76
|
+
* a link to a section scrolls to it. Absent, `section-<n>`.
|
|
77
|
+
*/
|
|
78
|
+
sectionIds?: readonly string[];
|
|
79
|
+
}
|
|
80
|
+
export interface AiLayoutCompiledPage {
|
|
81
|
+
tree: AiLayoutRawTree;
|
|
82
|
+
/** Each plan section's root, by its compiled id, in plan order. */
|
|
83
|
+
sectionRoots: string[];
|
|
84
|
+
/** A button's compiled id → the plan section a click scrolls to. */
|
|
85
|
+
scrollTo: Record<string, number>;
|
|
86
|
+
settled: AiLayoutSettlement[];
|
|
87
|
+
}
|
|
88
|
+
/** The most pictures a page carries; each beyond it is one more empty slot to fill. */
|
|
89
|
+
export declare const AI_LAYOUT_MAX_IMAGES = 3;
|
|
90
|
+
/** The most `brand` or `dark` bands a page shows; more and none stands out. */
|
|
91
|
+
export declare const AI_LAYOUT_MAX_STRONG_BANDS = 2;
|
|
92
|
+
/** Everything one page's compile shares. */
|
|
93
|
+
export interface PageScope {
|
|
94
|
+
tree: AiLayoutTreeBuilder;
|
|
95
|
+
plan: AiLayoutPagePlan;
|
|
96
|
+
targets: AiLayoutTargets;
|
|
97
|
+
options: AiLayoutCompileOptions;
|
|
98
|
+
settled: AiLayoutSettlement[];
|
|
99
|
+
scrollTo: Record<string, number>;
|
|
100
|
+
images: number;
|
|
101
|
+
strong: Record<'brand' | 'dark', number>;
|
|
102
|
+
/** The section of this page that places the site's form, if one does. */
|
|
103
|
+
formSection: number | null;
|
|
104
|
+
/** The icon a picture shows until the owner places one, when its block names none. */
|
|
105
|
+
pageIcon: AiIconLibraryEntry;
|
|
106
|
+
/** The forms this page already places, each once. */
|
|
107
|
+
formsPlaced: Set<string>;
|
|
108
|
+
}
|
|
109
|
+
/** Everything one section's compile shares. */
|
|
110
|
+
export interface SectionScope {
|
|
111
|
+
page: PageScope;
|
|
112
|
+
index: number;
|
|
113
|
+
at: string;
|
|
114
|
+
band: AiLayoutBand;
|
|
115
|
+
centered: boolean;
|
|
116
|
+
/** The level of the section's own heading: 1 on the first section, 2 after. */
|
|
117
|
+
level: number;
|
|
118
|
+
/**
|
|
119
|
+
* A section of the site's frame — its footer — whose headings are labels:
|
|
120
|
+
* a layout carries no heading outline of its own, since every page brings
|
|
121
|
+
* its own (rule 11).
|
|
122
|
+
*/
|
|
123
|
+
frame?: boolean;
|
|
124
|
+
/** Whether the section's own heading has been written. */
|
|
125
|
+
headed: boolean;
|
|
126
|
+
link: AiLayoutLinkScope;
|
|
127
|
+
/** Like items the section may still show (rule 8), across its groups. */
|
|
128
|
+
itemsLeft: number;
|
|
129
|
+
}
|
|
130
|
+
/** Compiles a page designed in the layout language into the tree it stores. */
|
|
131
|
+
export declare function aiCompileLayoutPage(sections: readonly AiLayoutSection[], plan: AiLayoutPagePlan, targets: AiLayoutTargets, options: AiLayoutCompileOptions): AiLayoutCompiledPage;
|
|
132
|
+
/** A band's own colors, as palette roles: the dark band pins the scheme instead. */
|
|
133
|
+
export declare function bandSx(band: AiLayoutBand): Record<string, unknown> | null;
|
|
134
|
+
/** Spans out of twelve for relative widths: each at least three, summing to twelve. */
|
|
135
|
+
export declare function aiLayoutSpans(weights: readonly number[]): number[];
|
|
136
|
+
export declare function compileRow(scope: SectionScope, columns: Array<{
|
|
137
|
+
column: AiLayoutBlock[];
|
|
138
|
+
weight: number;
|
|
139
|
+
}>): string;
|
|
140
|
+
export type Room = 'full' | 'half' | 'narrow';
|
|
141
|
+
/** A column of blocks, top to bottom, with buttons side by side; `null` when nothing in it compiles. */
|
|
142
|
+
export declare function compileFlow(scope: SectionScope, blocks: readonly AiLayoutBlock[], room: Room): string | null;
|
|
143
|
+
export declare function destinationProps(destination: AiLayoutDestination): Record<string, unknown>;
|
|
144
|
+
/** The id the compiled page's root is stored under. */
|
|
145
|
+
export declare const AI_LAYOUT_PAGE_ROOT_ID = "_@_";
|