@aglyn/plugins-ai 1.0.0-beta.257 → 1.0.0-beta.259
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/assist-panel.component.js +21 -8
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/insights/ai-figure-readers.js +15 -8
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +9 -0
- package/src/lib/jobs/ai-job-build-step.js +19 -3
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +1 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +33 -0
- package/src/lib/jobs/ai-job-page-language.js +46 -6
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +12 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +14 -0
- package/src/lib/jobs/ai-job-page-step.js +79 -39
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +3 -2
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +7 -1
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +10 -5
- package/src/lib/jobs/ai-layout-stock-photos.js +89 -7
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js +126 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +52 -52
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +9 -5
- package/src/lib/layout-language/ai-layout-copy.js +22 -14
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +4 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-motion.d.ts +75 -0
- package/src/lib/layout-language/ai-layout-motion.js +172 -0
- package/src/lib/layout-language/ai-layout-motion.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +44 -15
- package/src/lib/layout-language/ai-layout-pictures.js +29 -8
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-build-job.js +14 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-host-publish-context.d.ts +6 -0
- package/src/lib/model/ai-host-publish-context.js +15 -2
- package/src/lib/model/ai-host-publish-context.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +35 -0
- package/src/lib/model/assist-follow-up.js +44 -4
- package/src/lib/model/assist-follow-up.js.map +1 -1
- package/src/lib/operator-alerts.js +1 -1
- package/src/lib/operator-alerts.js.map +1 -1
- package/src/lib/providers/anthropic.js +17 -4
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/cache-write-hour.d.ts +37 -0
- package/src/lib/providers/cache-write-hour.js +46 -0
- package/src/lib/providers/cache-write-hour.js.map +1 -0
- package/src/lib/providers/contract.d.ts +16 -3
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.d.ts +13 -0
- package/src/lib/providers/routing.js +27 -11
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dev-env.d.ts +13 -3
- package/src/lib/runtime/ai-dev-env.js +17 -3
- package/src/lib/runtime/ai-dev-env.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +70 -1
- package/src/lib/runtime/ai-doctrine-validators.js +332 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +12 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +1 -1
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +1 -1
- package/src/lib/runtime/ai-eval.js +18 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-placeholder-tokens.d.ts +70 -0
- package/src/lib/runtime/ai-placeholder-tokens.js +169 -0
- package/src/lib/runtime/ai-placeholder-tokens.js.map +1 -0
- package/src/lib/runtime/ai-repeated-items.js +5 -2
- package/src/lib/runtime/ai-repeated-items.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +2 -3
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/server/assist-chat.js +8 -3
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.d.ts +21 -0
- package/src/lib/server/assist-edit.js +99 -21
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +5 -1
- package/src/lib/server/assist-view-context.js +8 -3
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +2 -0
- package/src/lib/usage/assist-usage.js +8 -3
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -57,17 +57,21 @@ export declare function aiLayoutCleanText(value: unknown): string;
|
|
|
57
57
|
* word the job was given — the brief, the site's answers and its profile.
|
|
58
58
|
*/
|
|
59
59
|
export declare function aiLayoutGuardContacts(line: string, facts: string): string;
|
|
60
|
-
/**
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A line fitted to its place with invented contact details written as gaps;
|
|
62
|
+
* `language` is the site's (see `aiLayoutFitText`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function aiLayoutWords(value: unknown, place: AiLayoutTextPlace, facts: string, language?: string): string;
|
|
62
65
|
/**
|
|
63
66
|
* A line held to the characters its place holds (see `fitText`), and none at
|
|
64
|
-
* all where what is left is filler.
|
|
67
|
+
* all where what is left is filler. `language` is the site's language code,
|
|
68
|
+
* which says what a dangling word is (AGL-3828); absent, English.
|
|
65
69
|
*/
|
|
66
|
-
export declare function aiLayoutFitText(value: unknown, place: AiLayoutTextPlace): string;
|
|
70
|
+
export declare function aiLayoutFitText(value: unknown, place: AiLayoutTextPlace, language?: string): string;
|
|
67
71
|
/**
|
|
68
72
|
* Whether a line is filler rule 14 refuses ("lorem ipsum", "your text here"):
|
|
69
73
|
* such a line is no copy, and the compiler leaves it out rather than ship it.
|
|
70
74
|
*/
|
|
71
75
|
export declare function aiLayoutIsFiller(text: string): boolean;
|
|
72
76
|
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */
|
|
73
|
-
export declare function aiLayoutQuoteGap(value: unknown): string;
|
|
77
|
+
export declare function aiLayoutQuoteGap(value: unknown, language?: string): string;
|
|
@@ -49,10 +49,14 @@
|
|
|
49
49
|
return value.replace(MARKUP, '')// A JSON escape a model wrote out as text ("\\u2014") is the character it names.
|
|
50
50
|
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex)=>String.fromCharCode(parseInt(hex, 16))).replace(/\s+/g, ' ').trim();
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The line with every trailing dangling word taken off, as rule 14 reads one,
|
|
54
|
+
* in the site's language (AGL-3828): a Spanish line cut to "…para la" loses
|
|
55
|
+
* both words, as an English one cut to "…for the" does.
|
|
56
|
+
*/ function withoutDanglingTail(line, language) {
|
|
53
57
|
let text = line.replace(TRAILING_JOINERS, '').trim();
|
|
54
58
|
for(let guard = 0; guard < 6; guard += 1){
|
|
55
|
-
const word = aiDanglingWord(text);
|
|
59
|
+
const word = aiDanglingWord(text, language);
|
|
56
60
|
if (!word) break;
|
|
57
61
|
text = text.slice(0, text.length - word.length).replace(TRAILING_JOINERS, '').trim();
|
|
58
62
|
}
|
|
@@ -63,10 +67,10 @@
|
|
|
63
67
|
* a line to end: at the last sentence's end that leaves at least half the
|
|
64
68
|
* room, else the last clause boundary that does, else the last whole word. A
|
|
65
69
|
* line that fits is returned clean, with any dangling tail taken off.
|
|
66
|
-
*/ function fitText(value, place) {
|
|
70
|
+
*/ function fitText(value, place, language) {
|
|
67
71
|
const text = aiLayoutCleanText(value);
|
|
68
72
|
const limit = AI_LAYOUT_TEXT_LIMITS[place];
|
|
69
|
-
if (text.length <= limit) return withoutDanglingTail(text);
|
|
73
|
+
if (text.length <= limit) return withoutDanglingTail(text, language);
|
|
70
74
|
const room = text.slice(0, limit + 1);
|
|
71
75
|
const half = Math.floor(limit * 0.4);
|
|
72
76
|
const lastMatch = (pattern)=>{
|
|
@@ -79,11 +83,11 @@
|
|
|
79
83
|
return at;
|
|
80
84
|
};
|
|
81
85
|
const sentence = lastMatch(SENTENCE_END);
|
|
82
|
-
if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence));
|
|
86
|
+
if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence), language);
|
|
83
87
|
const clause = lastMatch(CLAUSE_END);
|
|
84
|
-
if (clause > 0) return withoutDanglingTail(room.slice(0, clause));
|
|
88
|
+
if (clause > 0) return withoutDanglingTail(room.slice(0, clause), language);
|
|
85
89
|
const space = room.lastIndexOf(' ', limit);
|
|
86
|
-
return withoutDanglingTail(space > half ? room.slice(0, space) : room.slice(0, limit));
|
|
90
|
+
return withoutDanglingTail(space > half ? room.slice(0, space) : room.slice(0, limit), language);
|
|
87
91
|
}
|
|
88
92
|
/** A phone number as people write one: seven or more digits with the usual separators. */ const PHONE = /\(?\+?\d[\d\s().-]{6,}\d/g;
|
|
89
93
|
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
@@ -106,14 +110,18 @@ const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
|
106
110
|
return knownDigits.has(digits) ? phone : '[phone number]';
|
|
107
111
|
});
|
|
108
112
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
113
|
+
/**
|
|
114
|
+
* A line fitted to its place with invented contact details written as gaps;
|
|
115
|
+
* `language` is the site's (see `aiLayoutFitText`).
|
|
116
|
+
*/ export function aiLayoutWords(value, place, facts, language) {
|
|
117
|
+
return aiLayoutFitText(aiLayoutGuardContacts(aiLayoutCleanText(value), facts), place, language);
|
|
111
118
|
}
|
|
112
119
|
/**
|
|
113
120
|
* A line held to the characters its place holds (see `fitText`), and none at
|
|
114
|
-
* all where what is left is filler.
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
* all where what is left is filler. `language` is the site's language code,
|
|
122
|
+
* which says what a dangling word is (AGL-3828); absent, English.
|
|
123
|
+
*/ export function aiLayoutFitText(value, place, language) {
|
|
124
|
+
const text = fitText(value, place, language);
|
|
117
125
|
return aiLayoutIsFiller(text) ? '' : text;
|
|
118
126
|
}
|
|
119
127
|
/**
|
|
@@ -127,8 +135,8 @@ const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
|
127
135
|
}
|
|
128
136
|
]).length > 0;
|
|
129
137
|
}
|
|
130
|
-
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */ export function aiLayoutQuoteGap(value) {
|
|
131
|
-
const text = aiLayoutFitText(value, 'quote').replace(/^["“'‘]+|["”'’]+$/g, '').trim();
|
|
138
|
+
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */ export function aiLayoutQuoteGap(value, language) {
|
|
139
|
+
const text = aiLayoutFitText(value, 'quote', language).replace(/^["“'‘]+|["”'’]+$/g, '').trim();
|
|
132
140
|
if (!text) return '';
|
|
133
141
|
if (/^\[.*\]$/.test(text)) return text;
|
|
134
142
|
return `[${text.replace(/^\[|\]$/g, '')}]`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-copy.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 {\n aiDanglingWord,\n detectOffVoiceCopy,\n} from '../runtime/ai-doctrine-validators'\n\n/**\n * The words of a layout language document, made to fit where they are shown\n * (AGL-3660). Every word is the model's; the compiler only cleans and fits\n * them: markup a person would see is taken out, a line too long for its place\n * ends at a clean boundary instead of mid-word, a line that would end on a\n * dangling word (\"…for you and\") loses it, and a phone number or an email\n * address the brief never gave is written as the bracketed gap the doctrine\n * asks for (rule 14) rather than shown as a fact.\n */\n\n/** Characters each place holds before its line is ended. */\nexport const AI_LAYOUT_TEXT_LIMITS = {\n /** A page's h1. */\n title: 70,\n /** A section's heading. */\n heading: 80,\n /** A card's, a step's or a sub-heading's title. */\n itemTitle: 60,\n eyebrow: 40,\n /** A button's label; the palette holds a label to 40. */\n label: 28,\n lede: 240,\n text: 420,\n itemText: 240,\n listItem: 140,\n stat: 14,\n statLabel: 60,\n question: 120,\n answer: 420,\n quote: 240,\n note: 200,\n /** An image's description, which is its alt text. */\n alt: 160,\n} as const\n\nexport type AiLayoutTextPlace = keyof typeof AI_LAYOUT_TEXT_LIMITS\n\n/** Inline markup a model writes out of habit, which a page would print literally. */\nconst MARKUP = /(\\*\\*|__|`|~~|^#{1,6}\\s+|^\\s*[-*•]\\s+)/gm\n\n/** A sentence's end inside a line. */\nconst SENTENCE_END = /[.!?…](?=[\"'”’)\\]]?\\s)/g\n\n/** A clause boundary inside a line: a comma, a semicolon, a colon or a dash, with what follows. */\nconst CLAUSE_END = /\\s*(?:[,;:]|\\s[—–-])\\s/g\n\n/** Punctuation that may not end a line it was cut back to. */\nconst TRAILING_JOINERS = /[\\s,;:—–-]+$/\n\n/** Collapses whitespace and takes out markup a page would print. */\nexport function aiLayoutCleanText(value: unknown): string {\n if (typeof value !== 'string') return ''\n return value\n .replace(MARKUP, '')\n // A JSON escape a model wrote out as text (\"\\\\u2014\") is the character it names.\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** The line with every trailing dangling word taken off, as rule 14 reads one. */\nfunction withoutDanglingTail(line: string): string {\n let text = line.replace(TRAILING_JOINERS, '').trim()\n for (let guard = 0; guard < 6; guard += 1) {\n const word = aiDanglingWord(text)\n if (!word) break\n text = text\n .slice(0, text.length - word.length)\n .replace(TRAILING_JOINERS, '')\n .trim()\n }\n return text\n}\n\n/**\n * A line held to the characters its place holds, ended where a reader expects\n * a line to end: at the last sentence's end that leaves at least half the\n * room, else the last clause boundary that does, else the last whole word. A\n * line that fits is returned clean, with any dangling tail taken off.\n */\nfunction fitText(value: unknown, place: AiLayoutTextPlace): string {\n const text = aiLayoutCleanText(value)\n const limit = AI_LAYOUT_TEXT_LIMITS[place]\n if (text.length <= limit) return withoutDanglingTail(text)\n const room = text.slice(0, limit + 1)\n const half = Math.floor(limit * 0.4)\n const lastMatch = (pattern: RegExp): number => {\n let at = -1\n for (const match of room.matchAll(pattern)) {\n const end = (match.index ?? 0) + (pattern === SENTENCE_END ? 1 : 0)\n if (end <= limit && end >= half) at = end\n }\n return at\n }\n const sentence = lastMatch(SENTENCE_END)\n if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence))\n const clause = lastMatch(CLAUSE_END)\n if (clause > 0) return withoutDanglingTail(room.slice(0, clause))\n const space = room.lastIndexOf(' ', limit)\n return withoutDanglingTail(\n space > half ? room.slice(0, space) : room.slice(0, limit),\n )\n}\n\n/** A phone number as people write one: seven or more digits with the usual separators. */\nconst PHONE = /\\(?\\+?\\d[\\d\\s().-]{6,}\\d/g\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g\n\n/** The digits of a phone number, which is what two spellings of one number share. */\nfunction digitsOf(value: string): string {\n return value.replace(/\\D/g, '')\n}\n\n/**\n * The line with every phone number and email address the brief did not give\n * written as the gap (rule 14): a site's contact details are facts, and a\n * model that writes one the brief lacks is inventing it. `facts` is every\n * word the job was given — the brief, the site's answers and its profile.\n */\nexport function aiLayoutGuardContacts(line: string, facts: string): string {\n const known = facts.toLowerCase()\n const knownDigits = new Set((facts.match(PHONE) ?? []).map(digitsOf))\n return line\n .replace(EMAIL, (email) =>\n known.includes(email.toLowerCase()) ? email : '[email address]',\n )\n .replace(PHONE, (phone) => {\n const digits = digitsOf(phone)\n // A year, a price or a count is not a phone number.\n if (digits.length < 7) return phone\n return knownDigits.has(digits) ? phone : '[phone number]'\n })\n}\n\n/** A line fitted to its place with invented contact details written as gaps. */\nexport function aiLayoutWords(\n value: unknown,\n place: AiLayoutTextPlace,\n facts: string,\n): string {\n return aiLayoutFitText(\n aiLayoutGuardContacts(aiLayoutCleanText(value), facts),\n place,\n )\n}\n\n/**\n * A line held to the characters its place holds (see `fitText`), and none at\n * all where what is left is filler.\n */\nexport function aiLayoutFitText(\n value: unknown,\n place: AiLayoutTextPlace,\n): string {\n const text = fitText(value, place)\n return aiLayoutIsFiller(text) ? '' : text\n}\n\n/**\n * Whether a line is filler rule 14 refuses (\"lorem ipsum\", \"your text here\"):\n * such a line is no copy, and the compiler leaves it out rather than ship it.\n */\nexport function aiLayoutIsFiller(text: string): boolean {\n return detectOffVoiceCopy([{ at: 'line', text }]).length > 0\n}\n\n/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */\nexport function aiLayoutQuoteGap(value: unknown): string {\n const text = aiLayoutFitText(value, 'quote')\n .replace(/^[\"“'‘]+|[\"”'’]+$/g, '')\n .trim()\n if (!text) return ''\n if (/^\\[.*\\]$/.test(text)) return text\n return `[${text.replace(/^\\[|\\]$/g, '')}]`\n}\n"],"names":["aiDanglingWord","detectOffVoiceCopy","AI_LAYOUT_TEXT_LIMITS","title","heading","itemTitle","eyebrow","label","lede","text","itemText","listItem","stat","statLabel","question","answer","quote","note","alt","MARKUP","SENTENCE_END","CLAUSE_END","TRAILING_JOINERS","aiLayoutCleanText","value","replace","_","hex","String","fromCharCode","parseInt","trim","withoutDanglingTail","line","guard","word","slice","length","fitText","place","limit","room","half","Math","floor","lastMatch","pattern","at","match","matchAll","end","index","sentence","clause","space","lastIndexOf","PHONE","EMAIL","digitsOf","aiLayoutGuardContacts","facts","known","toLowerCase","knownDigits","Set","map","email","includes","phone","digits","has","aiLayoutWords","aiLayoutFitText","aiLayoutIsFiller","aiLayoutQuoteGap","test"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-copy.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 {\n aiDanglingWord,\n detectOffVoiceCopy,\n} from '../runtime/ai-doctrine-validators'\n\n/**\n * The words of a layout language document, made to fit where they are shown\n * (AGL-3660). Every word is the model's; the compiler only cleans and fits\n * them: markup a person would see is taken out, a line too long for its place\n * ends at a clean boundary instead of mid-word, a line that would end on a\n * dangling word (\"…for you and\") loses it, and a phone number or an email\n * address the brief never gave is written as the bracketed gap the doctrine\n * asks for (rule 14) rather than shown as a fact.\n */\n\n/** Characters each place holds before its line is ended. */\nexport const AI_LAYOUT_TEXT_LIMITS = {\n /** A page's h1. */\n title: 70,\n /** A section's heading. */\n heading: 80,\n /** A card's, a step's or a sub-heading's title. */\n itemTitle: 60,\n eyebrow: 40,\n /** A button's label; the palette holds a label to 40. */\n label: 28,\n lede: 240,\n text: 420,\n itemText: 240,\n listItem: 140,\n stat: 14,\n statLabel: 60,\n question: 120,\n answer: 420,\n quote: 240,\n note: 200,\n /** An image's description, which is its alt text. */\n alt: 160,\n} as const\n\nexport type AiLayoutTextPlace = keyof typeof AI_LAYOUT_TEXT_LIMITS\n\n/** Inline markup a model writes out of habit, which a page would print literally. */\nconst MARKUP = /(\\*\\*|__|`|~~|^#{1,6}\\s+|^\\s*[-*•]\\s+)/gm\n\n/** A sentence's end inside a line. */\nconst SENTENCE_END = /[.!?…](?=[\"'”’)\\]]?\\s)/g\n\n/** A clause boundary inside a line: a comma, a semicolon, a colon or a dash, with what follows. */\nconst CLAUSE_END = /\\s*(?:[,;:]|\\s[—–-])\\s/g\n\n/** Punctuation that may not end a line it was cut back to. */\nconst TRAILING_JOINERS = /[\\s,;:—–-]+$/\n\n/** Collapses whitespace and takes out markup a page would print. */\nexport function aiLayoutCleanText(value: unknown): string {\n if (typeof value !== 'string') return ''\n return value\n .replace(MARKUP, '')\n // A JSON escape a model wrote out as text (\"\\\\u2014\") is the character it names.\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * The line with every trailing dangling word taken off, as rule 14 reads one,\n * in the site's language (AGL-3828): a Spanish line cut to \"…para la\" loses\n * both words, as an English one cut to \"…for the\" does.\n */\nfunction withoutDanglingTail(line: string, language?: string): string {\n let text = line.replace(TRAILING_JOINERS, '').trim()\n for (let guard = 0; guard < 6; guard += 1) {\n const word = aiDanglingWord(text, language)\n if (!word) break\n text = text\n .slice(0, text.length - word.length)\n .replace(TRAILING_JOINERS, '')\n .trim()\n }\n return text\n}\n\n/**\n * A line held to the characters its place holds, ended where a reader expects\n * a line to end: at the last sentence's end that leaves at least half the\n * room, else the last clause boundary that does, else the last whole word. A\n * line that fits is returned clean, with any dangling tail taken off.\n */\nfunction fitText(value: unknown, place: AiLayoutTextPlace, language?: string): string {\n const text = aiLayoutCleanText(value)\n const limit = AI_LAYOUT_TEXT_LIMITS[place]\n if (text.length <= limit) return withoutDanglingTail(text, language)\n const room = text.slice(0, limit + 1)\n const half = Math.floor(limit * 0.4)\n const lastMatch = (pattern: RegExp): number => {\n let at = -1\n for (const match of room.matchAll(pattern)) {\n const end = (match.index ?? 0) + (pattern === SENTENCE_END ? 1 : 0)\n if (end <= limit && end >= half) at = end\n }\n return at\n }\n const sentence = lastMatch(SENTENCE_END)\n if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence), language)\n const clause = lastMatch(CLAUSE_END)\n if (clause > 0) return withoutDanglingTail(room.slice(0, clause), language)\n const space = room.lastIndexOf(' ', limit)\n return withoutDanglingTail(\n space > half ? room.slice(0, space) : room.slice(0, limit),\n language,\n )\n}\n\n/** A phone number as people write one: seven or more digits with the usual separators. */\nconst PHONE = /\\(?\\+?\\d[\\d\\s().-]{6,}\\d/g\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g\n\n/** The digits of a phone number, which is what two spellings of one number share. */\nfunction digitsOf(value: string): string {\n return value.replace(/\\D/g, '')\n}\n\n/**\n * The line with every phone number and email address the brief did not give\n * written as the gap (rule 14): a site's contact details are facts, and a\n * model that writes one the brief lacks is inventing it. `facts` is every\n * word the job was given — the brief, the site's answers and its profile.\n */\nexport function aiLayoutGuardContacts(line: string, facts: string): string {\n const known = facts.toLowerCase()\n const knownDigits = new Set((facts.match(PHONE) ?? []).map(digitsOf))\n return line\n .replace(EMAIL, (email) =>\n known.includes(email.toLowerCase()) ? email : '[email address]',\n )\n .replace(PHONE, (phone) => {\n const digits = digitsOf(phone)\n // A year, a price or a count is not a phone number.\n if (digits.length < 7) return phone\n return knownDigits.has(digits) ? phone : '[phone number]'\n })\n}\n\n/**\n * A line fitted to its place with invented contact details written as gaps;\n * `language` is the site's (see `aiLayoutFitText`).\n */\nexport function aiLayoutWords(\n value: unknown,\n place: AiLayoutTextPlace,\n facts: string,\n language?: string,\n): string {\n return aiLayoutFitText(\n aiLayoutGuardContacts(aiLayoutCleanText(value), facts),\n place,\n language,\n )\n}\n\n/**\n * A line held to the characters its place holds (see `fitText`), and none at\n * all where what is left is filler. `language` is the site's language code,\n * which says what a dangling word is (AGL-3828); absent, English.\n */\nexport function aiLayoutFitText(\n value: unknown,\n place: AiLayoutTextPlace,\n language?: string,\n): string {\n const text = fitText(value, place, language)\n return aiLayoutIsFiller(text) ? '' : text\n}\n\n/**\n * Whether a line is filler rule 14 refuses (\"lorem ipsum\", \"your text here\"):\n * such a line is no copy, and the compiler leaves it out rather than ship it.\n */\nexport function aiLayoutIsFiller(text: string): boolean {\n return detectOffVoiceCopy([{ at: 'line', text }]).length > 0\n}\n\n/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */\nexport function aiLayoutQuoteGap(value: unknown, language?: string): string {\n const text = aiLayoutFitText(value, 'quote', language)\n .replace(/^[\"“'‘]+|[\"”'’]+$/g, '')\n .trim()\n if (!text) return ''\n if (/^\\[.*\\]$/.test(text)) return text\n return `[${text.replace(/^\\[|\\]$/g, '')}]`\n}\n"],"names":["aiDanglingWord","detectOffVoiceCopy","AI_LAYOUT_TEXT_LIMITS","title","heading","itemTitle","eyebrow","label","lede","text","itemText","listItem","stat","statLabel","question","answer","quote","note","alt","MARKUP","SENTENCE_END","CLAUSE_END","TRAILING_JOINERS","aiLayoutCleanText","value","replace","_","hex","String","fromCharCode","parseInt","trim","withoutDanglingTail","line","language","guard","word","slice","length","fitText","place","limit","room","half","Math","floor","lastMatch","pattern","at","match","matchAll","end","index","sentence","clause","space","lastIndexOf","PHONE","EMAIL","digitsOf","aiLayoutGuardContacts","facts","known","toLowerCase","knownDigits","Set","map","email","includes","phone","digits","has","aiLayoutWords","aiLayoutFitText","aiLayoutIsFiller","aiLayoutQuoteGap","test"],"mappings":"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"}
|
|
@@ -139,7 +139,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
139
139
|
splitAt: -2,
|
|
140
140
|
quotesOnly: []
|
|
141
141
|
};
|
|
142
|
-
const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home';
|
|
142
|
+
const name = aiLayoutFitText(plan.siteName, 'heading', plan.language) || 'Home';
|
|
143
143
|
const header = (_frame_header = frame.header) != null ? _frame_header : {
|
|
144
144
|
blocks: []
|
|
145
145
|
};
|
|
@@ -148,7 +148,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
148
148
|
const scope = frameScope(page, 'header', band === 'brand' || band === 'dark' ? 'brand' : 'plain', false);
|
|
149
149
|
const navLink = (entry, role, interactions)=>{
|
|
150
150
|
const id = tree.add('muiScreenLink', _extends({
|
|
151
|
-
children: aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28)
|
|
151
|
+
children: aiLayoutFitText(entry.label, 'label', plan.language) || entry.label.slice(0, 28)
|
|
152
152
|
}, entry.href ? {
|
|
153
153
|
href: entry.href
|
|
154
154
|
} : {
|
|
@@ -210,7 +210,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
210
210
|
what: `a ${block.kind} in the header; left out`
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
-
const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : '';
|
|
213
|
+
const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label', plan.language) : '';
|
|
214
214
|
const ctaResolved = ctaBlock && ctaLabel ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets) : null;
|
|
215
215
|
if (ctaBlock && !ctaResolved) page.settled.push({
|
|
216
216
|
at: 'header',
|
|
@@ -349,7 +349,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
349
349
|
], 'header');
|
|
350
350
|
// A selling store's announcement bar, above the header, linking its shop (AGL-3676).
|
|
351
351
|
const announcement = store && plan.cart && plan.shopId ? (()=>{
|
|
352
|
-
const said = aiLayoutFitText(noteBlock == null ? void 0 : noteBlock.text, 'note');
|
|
352
|
+
const said = aiLayoutFitText(noteBlock == null ? void 0 : noteBlock.text, 'note', plan.language);
|
|
353
353
|
if (noteBlock && !said) page.settled.push({
|
|
354
354
|
at: 'header',
|
|
355
355
|
what: 'an announcement with no words left; the shop’s own line instead'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.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 { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { aiLayoutRepairFrameHollows } from './ai-layout-frame-hollows'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from './ai-layout-labels'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n /**\n * A store's own pages (AGL-3676), by path: the header links the account\n * beside the cart, and the footer the account and the store's policies\n * under the site's pages. They are written after the layout, so they are\n * linked by their addresses, as the blog is.\n */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language (AGL-3760): the frame's own words are in it where `ai-layout-labels.ts` lists it. */\n language?: string\n}\n\n/** What the header's link to a store's account page says (AGL-3676), in English. */\nexport const AI_LAYOUT_ACCOUNT_LINK = AI_LAYOUT_LABELS_EN.account\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = AI_LAYOUT_LABELS_EN.storeAnnouncement\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = AI_LAYOUT_LABELS_EN.shopNow\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n // A store's own pages are destinations the footer's list links by path (AGL-3676).\n const storePages: AiLayoutPage[] = (plan.storeLinks?.footer ?? []).map((link) => ({\n id: `store:${link.href}`,\n label: link.label,\n slug: link.href,\n href: link.href,\n }))\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pages: [...targets.pages, ...storePages], pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true, ...(plan.language ? { language: plan.language } : {}) },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A store's account, beside its cart and in the phone's menu (AGL-3676).\n const accountPath = plan.storeLinks?.account ?? null\n const accountLink = (role: string, interactions?: unknown[], sx?: Record<string, unknown>) => {\n const id = tree.add(\n 'muiScreenLink',\n { children: aiLayoutLabel('account', plan.language), href: accountPath, renderAs: 'link', color: 'inherit' },\n { textDecoration: 'none', whiteSpace: 'nowrap', ...sx },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n accountPath ? accountLink('menuAccount', close) : null,\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // On a phone the account is in the menu, and the cart stays in the bar.\n const account = accountPath ? accountLink('account', undefined, { display: { xs: 'none', md: 'inline-flex' }, flexShrink: 0 }) : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, account, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note')\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: aiLayoutLabel(said ? 'shopNow' : 'storeAnnouncement', plan.language),\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n // No heading with nothing under it, no column of only a heading, columns\n // top-aligned (AGL-3735): a list under a heading can compile to nothing.\n if (aiLayoutRepairFrameHollows(tree.nodes as unknown as Record<string, unknown>)) {\n page.settled.push({ at: 'footer', what: 'a heading with nothing under it taken out, and its column with it' })\n }\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */\nexport const AI_LAYOUT_STORE_FOOTER_HEADING = AI_LAYOUT_LABELS_EN.customerCare\n\n/** A footer heading a store's account and policies belong under (AGL-3735). */\nconst STORE_HEADING = /\\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\\b/i\n\n/**\n * The footer heading a store's account and policy links go under\n * (AGL-3735): a heading of the design's that names the store or its help\n * (\"Our shop\", \"Help\", \"Customer care\"), the first with nothing under it\n * before one with words or links of its own. Never the heading over the\n * site's pages, whose list is the site's navigation. `end` is where the\n * links go: the end of what that heading heads.\n */\nexport function aiLayoutFooterStoreHeading(\n columns: readonly AiLayoutBlock[][],\n navItems: readonly { to: string }[],\n): { column: AiLayoutBlock[]; end: number; heading: string } | null {\n const navTo = new Set(navItems.map((item) => item.to))\n const candidates: Array<{ column: AiLayoutBlock[]; end: number; heading: string; empty: boolean }> = []\n for (const column of columns) {\n column.forEach((block, at) => {\n if (block.kind !== 'heading' || !STORE_HEADING.test(block.text ?? '')) return\n let end = at + 1\n while (end < column.length && column[end].kind !== 'heading') end += 1\n const under = column.slice(at + 1, end)\n const site = under.some((one) => one.kind === 'list' && (one.items ?? []).some((item) => navTo.has(item.to ?? '')))\n if (site) return\n candidates.push({ column: column as AiLayoutBlock[], end, heading: String(block.text), empty: under.length === 0 })\n })\n }\n const pick = candidates.find((one) => one.empty) ?? candidates[0]\n return pick ? { column: pick.column, end: pick.end, heading: pick.heading } : null\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n // A store's account and policies (AGL-3676): under the store heading the\n // design already wrote (\"Our shop\", \"Help\"…), never as a column with no\n // heading beside a heading with nothing under it (AGL-3735); where the\n // design wrote none, a column of their own, headed.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n const list: AiLayoutBlock = { kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }\n const home = aiLayoutFooterStoreHeading(columns, navItems)\n if (home) {\n home.column.splice(home.end, 0, list)\n page.settled.push({ at: 'footer', what: `the store's account and policy links under \"${home.heading}\"` })\n } else {\n columns.push([{ kind: 'heading', text: aiLayoutLabel('customerCare', plan.language) }, list])\n weights.push(1)\n }\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n // A footer's columns are top-aligned: each starts at its heading (AGL-3735).\n const top =\n filled.length > 1\n ? compileRow(scope, filled, 'flex-start')\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","aiLayoutRepairFrameHollows","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_LABELS_EN","aiLayoutLabel","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_ACCOUNT_LINK","account","AI_LAYOUT_STORE_ANNOUNCEMENT","storeAnnouncement","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","shopNow","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","storePages","storeLinks","footer","map","id","href","label","slug","title","siteName","pages","pageId","options","reusableComponents","language","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","add","children","slice","screenId","renderAs","textDecoration","nodes","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","accountPath","accountLink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","compileFooter","column","prune","rootId","AI_LAYOUT_STORE_FOOTER_HEADING","customerCare","STORE_HEADING","aiLayoutFooterStoreHeading","columns","navItems","candidates","navTo","item","forEach","end","under","site","one","items","has","heading","String","empty","pick","enabled","trigger","event","everyTime","steps","type","asked","closesDark","linksPages","flatMap","given","notice","pageLinks","cols","col","Math","min","weights","list","home","splice","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.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 { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { aiLayoutRepairFrameHollows } from './ai-layout-frame-hollows'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from './ai-layout-labels'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n /**\n * A store's own pages (AGL-3676), by path: the header links the account\n * beside the cart, and the footer the account and the store's policies\n * under the site's pages. They are written after the layout, so they are\n * linked by their addresses, as the blog is.\n */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language (AGL-3760): the frame's own words are in it where `ai-layout-labels.ts` lists it. */\n language?: string\n}\n\n/** What the header's link to a store's account page says (AGL-3676), in English. */\nexport const AI_LAYOUT_ACCOUNT_LINK = AI_LAYOUT_LABELS_EN.account\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = AI_LAYOUT_LABELS_EN.storeAnnouncement\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = AI_LAYOUT_LABELS_EN.shopNow\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n // A store's own pages are destinations the footer's list links by path (AGL-3676).\n const storePages: AiLayoutPage[] = (plan.storeLinks?.footer ?? []).map((link) => ({\n id: `store:${link.href}`,\n label: link.label,\n slug: link.href,\n href: link.href,\n }))\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pages: [...targets.pages, ...storePages], pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true, ...(plan.language ? { language: plan.language } : {}) },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading', plan.language) || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label', plan.language) || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label', plan.language) : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A store's account, beside its cart and in the phone's menu (AGL-3676).\n const accountPath = plan.storeLinks?.account ?? null\n const accountLink = (role: string, interactions?: unknown[], sx?: Record<string, unknown>) => {\n const id = tree.add(\n 'muiScreenLink',\n { children: aiLayoutLabel('account', plan.language), href: accountPath, renderAs: 'link', color: 'inherit' },\n { textDecoration: 'none', whiteSpace: 'nowrap', ...sx },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n accountPath ? accountLink('menuAccount', close) : null,\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // On a phone the account is in the menu, and the cart stays in the bar.\n const account = accountPath ? accountLink('account', undefined, { display: { xs: 'none', md: 'inline-flex' }, flexShrink: 0 }) : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, account, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note', plan.language)\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: aiLayoutLabel(said ? 'shopNow' : 'storeAnnouncement', plan.language),\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n // No heading with nothing under it, no column of only a heading, columns\n // top-aligned (AGL-3735): a list under a heading can compile to nothing.\n if (aiLayoutRepairFrameHollows(tree.nodes as unknown as Record<string, unknown>)) {\n page.settled.push({ at: 'footer', what: 'a heading with nothing under it taken out, and its column with it' })\n }\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */\nexport const AI_LAYOUT_STORE_FOOTER_HEADING = AI_LAYOUT_LABELS_EN.customerCare\n\n/** A footer heading a store's account and policies belong under (AGL-3735). */\nconst STORE_HEADING = /\\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\\b/i\n\n/**\n * The footer heading a store's account and policy links go under\n * (AGL-3735): a heading of the design's that names the store or its help\n * (\"Our shop\", \"Help\", \"Customer care\"), the first with nothing under it\n * before one with words or links of its own. Never the heading over the\n * site's pages, whose list is the site's navigation. `end` is where the\n * links go: the end of what that heading heads.\n */\nexport function aiLayoutFooterStoreHeading(\n columns: readonly AiLayoutBlock[][],\n navItems: readonly { to: string }[],\n): { column: AiLayoutBlock[]; end: number; heading: string } | null {\n const navTo = new Set(navItems.map((item) => item.to))\n const candidates: Array<{ column: AiLayoutBlock[]; end: number; heading: string; empty: boolean }> = []\n for (const column of columns) {\n column.forEach((block, at) => {\n if (block.kind !== 'heading' || !STORE_HEADING.test(block.text ?? '')) return\n let end = at + 1\n while (end < column.length && column[end].kind !== 'heading') end += 1\n const under = column.slice(at + 1, end)\n const site = under.some((one) => one.kind === 'list' && (one.items ?? []).some((item) => navTo.has(item.to ?? '')))\n if (site) return\n candidates.push({ column: column as AiLayoutBlock[], end, heading: String(block.text), empty: under.length === 0 })\n })\n }\n const pick = candidates.find((one) => one.empty) ?? candidates[0]\n return pick ? { column: pick.column, end: pick.end, heading: pick.heading } : null\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n // A store's account and policies (AGL-3676): under the store heading the\n // design already wrote (\"Our shop\", \"Help\"…), never as a column with no\n // heading beside a heading with nothing under it (AGL-3735); where the\n // design wrote none, a column of their own, headed.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n const list: AiLayoutBlock = { kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }\n const home = aiLayoutFooterStoreHeading(columns, navItems)\n if (home) {\n home.column.splice(home.end, 0, list)\n page.settled.push({ at: 'footer', what: `the store's account and policy links under \"${home.heading}\"` })\n } else {\n columns.push([{ kind: 'heading', text: aiLayoutLabel('customerCare', plan.language) }, list])\n weights.push(1)\n }\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n // A footer's columns are top-aligned: each starts at its heading (AGL-3735).\n const top =\n filled.length > 1\n ? compileRow(scope, filled, 'flex-start')\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","aiLayoutRepairFrameHollows","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_LABELS_EN","aiLayoutLabel","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_ACCOUNT_LINK","account","AI_LAYOUT_STORE_ANNOUNCEMENT","storeAnnouncement","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","shopNow","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","storePages","storeLinks","footer","map","id","href","label","slug","title","siteName","pages","pageId","options","reusableComponents","language","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","add","children","slice","screenId","renderAs","textDecoration","nodes","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","accountPath","accountLink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","compileFooter","column","prune","rootId","AI_LAYOUT_STORE_FOOTER_HEADING","customerCare","STORE_HEADING","aiLayoutFooterStoreHeading","columns","navItems","candidates","navTo","item","forEach","end","under","site","one","items","has","heading","String","empty","pick","enabled","trigger","event","everyTime","steps","type","asked","closesDark","linksPages","flatMap","given","notice","pageLinks","cols","col","Math","min","weights","list","home","splice","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 { AnimationEase, AnimationPreset } from '@aglyn/aglyn/app-utils/element-animation';
|
|
18
|
+
import { type ElementMotionCharacter } from '@aglyn/aglyn/app-utils/element-motion';
|
|
19
|
+
/**
|
|
20
|
+
* How an AI-built site moves (AGL-3838): a small motion CHARACTER per kind of
|
|
21
|
+
* site, turned by the site's design seed, which the platform's entrance pass
|
|
22
|
+
* (`element-motion.ts`) writes onto every page the AI builds.
|
|
23
|
+
*
|
|
24
|
+
* Decided in code, never by the model: no prompt names a preset, no answer
|
|
25
|
+
* carries one, and no credit is spent on it. The same site — same kind, same
|
|
26
|
+
* seed — always moves the same way on every page, and two sites of one kind
|
|
27
|
+
* are drawn apart by their seeds, the way their looks are.
|
|
28
|
+
*
|
|
29
|
+
* The moods, by the kinds `ai-site-kinds.ts` lists:
|
|
30
|
+
* - CALM — practices, health, reading: a fade, on a gentle curve, slower.
|
|
31
|
+
* - BOLD — studios, fitness, music, events: a rise with overshoot, and
|
|
32
|
+
* pictures that zoom in.
|
|
33
|
+
* - GALLERY — portfolios and photography: pictures zoom in, one after
|
|
34
|
+
* another; the words fade or rise.
|
|
35
|
+
* - STANDARD — everything else: a smooth rise.
|
|
36
|
+
*
|
|
37
|
+
* Every number stays inside the vocabulary's caps, and inside 400–800 ms for
|
|
38
|
+
* a duration and 60–120 ms between staggered items: slower reads as a site
|
|
39
|
+
* that is loading, faster as one that is twitching.
|
|
40
|
+
*/
|
|
41
|
+
export type AiSiteMotionMood = 'calm' | 'bold' | 'gallery' | 'standard';
|
|
42
|
+
interface AiSiteMotionRange {
|
|
43
|
+
/** The word presets the seed picks among. */
|
|
44
|
+
presets: readonly AnimationPreset[];
|
|
45
|
+
/** The picture presets the seed picks among. */
|
|
46
|
+
media: readonly AnimationPreset[];
|
|
47
|
+
eases: readonly AnimationEase[];
|
|
48
|
+
/** Inclusive millisecond ranges. */
|
|
49
|
+
duration: readonly [number, number];
|
|
50
|
+
stagger: readonly [number, number];
|
|
51
|
+
hero: readonly [number, number];
|
|
52
|
+
}
|
|
53
|
+
/** Each mood's range. The seed picks one character from it. */
|
|
54
|
+
export declare const AI_SITE_MOTION_MOODS: Readonly<Record<AiSiteMotionMood, AiSiteMotionRange>>;
|
|
55
|
+
/** Each site kind's mood; a kind not listed is standard. */
|
|
56
|
+
export declare const AI_SITE_MOTION_KINDS: Readonly<Record<string, AiSiteMotionMood>>;
|
|
57
|
+
/** The mood a kind of site moves in. */
|
|
58
|
+
export declare function aiSiteMotionMood(kind: string | null | undefined): AiSiteMotionMood;
|
|
59
|
+
/** The motion character of a site: its kind's mood, turned by its seed. */
|
|
60
|
+
export declare function aiSiteMotion(input: {
|
|
61
|
+
kind?: string | null;
|
|
62
|
+
seed: number;
|
|
63
|
+
}): ElementMotionCharacter;
|
|
64
|
+
/**
|
|
65
|
+
* A page the AI built, with its site's entrance motion written onto it —
|
|
66
|
+
* or the same node map, when there was nothing left to move.
|
|
67
|
+
*/
|
|
68
|
+
export declare function aiWithSiteMotion<T extends Record<string, any>>(nodes: T, site: {
|
|
69
|
+
kind?: string | null;
|
|
70
|
+
seed: number;
|
|
71
|
+
}, page?: {
|
|
72
|
+
rootId?: string;
|
|
73
|
+
sectionIds?: readonly string[];
|
|
74
|
+
}): T;
|
|
75
|
+
export {};
|