@aglyn/plugins-ai 1.0.0-beta.256 → 1.0.0-beta.258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/assist-panel.component.js +21 -8
  3. package/src/lib/components/assist-panel.component.js.map +1 -1
  4. package/src/lib/insights/ai-figure-readers.js +15 -8
  5. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  6. package/src/lib/jobs/ai-job-build-step.d.ts +9 -0
  7. package/src/lib/jobs/ai-job-build-step.js +19 -3
  8. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  9. package/src/lib/jobs/ai-job-form-step.js +3 -3
  10. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  11. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  12. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  13. package/src/lib/jobs/ai-job-page-language.d.ts +19 -0
  14. package/src/lib/jobs/ai-job-page-language.js +24 -4
  15. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  16. package/src/lib/jobs/ai-job-page-sections.js +12 -1
  17. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  18. package/src/lib/jobs/ai-job-page-step.d.ts +14 -0
  19. package/src/lib/jobs/ai-job-page-step.js +69 -20
  20. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-seo-step.js +3 -2
  22. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  23. package/src/lib/jobs/ai-layout-stock-photos.d.ts +10 -5
  24. package/src/lib/jobs/ai-layout-stock-photos.js +89 -7
  25. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  26. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.d.ts +21 -0
  27. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js +126 -0
  28. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js.map +1 -0
  29. package/src/lib/layout-language/ai-layout-compiler.js +52 -52
  30. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  31. package/src/lib/layout-language/ai-layout-copy.d.ts +9 -5
  32. package/src/lib/layout-language/ai-layout-copy.js +22 -14
  33. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  34. package/src/lib/layout-language/ai-layout-frame.js +4 -4
  35. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  36. package/src/lib/layout-language/ai-layout-pictures.d.ts +44 -15
  37. package/src/lib/layout-language/ai-layout-pictures.js +29 -8
  38. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  39. package/src/lib/model/ai-build-job.js +14 -0
  40. package/src/lib/model/ai-build-job.js.map +1 -1
  41. package/src/lib/model/ai-host-publish-context.d.ts +6 -0
  42. package/src/lib/model/ai-host-publish-context.js +15 -2
  43. package/src/lib/model/ai-host-publish-context.js.map +1 -1
  44. package/src/lib/model/assist-follow-up.d.ts +35 -0
  45. package/src/lib/model/assist-follow-up.js +44 -4
  46. package/src/lib/model/assist-follow-up.js.map +1 -1
  47. package/src/lib/providers/anthropic.js +17 -4
  48. package/src/lib/providers/anthropic.js.map +1 -1
  49. package/src/lib/providers/cache-write-hour.d.ts +37 -0
  50. package/src/lib/providers/cache-write-hour.js +46 -0
  51. package/src/lib/providers/cache-write-hour.js.map +1 -0
  52. package/src/lib/providers/contract.d.ts +16 -3
  53. package/src/lib/providers/contract.js.map +1 -1
  54. package/src/lib/providers/routing.d.ts +13 -0
  55. package/src/lib/providers/routing.js +27 -11
  56. package/src/lib/providers/routing.js.map +1 -1
  57. package/src/lib/runtime/ai-dev-env.d.ts +13 -3
  58. package/src/lib/runtime/ai-dev-env.js +17 -3
  59. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  60. package/src/lib/runtime/ai-dev-replay.js +4 -2
  61. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  62. package/src/lib/runtime/ai-doctrine-validators.d.ts +41 -1
  63. package/src/lib/runtime/ai-doctrine-validators.js +263 -2
  64. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  65. package/src/lib/runtime/ai-doctrine.js +7 -1
  66. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  67. package/src/lib/runtime/ai-eval-live.js +1 -1
  68. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  69. package/src/lib/runtime/ai-eval.d.ts +1 -1
  70. package/src/lib/runtime/ai-eval.js +18 -1
  71. package/src/lib/runtime/ai-eval.js.map +1 -1
  72. package/src/lib/runtime/ai-palette.generated.js +162 -2
  73. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  74. package/src/lib/runtime/ai-placeholder-tokens.d.ts +70 -0
  75. package/src/lib/runtime/ai-placeholder-tokens.js +169 -0
  76. package/src/lib/runtime/ai-placeholder-tokens.js.map +1 -0
  77. package/src/lib/runtime/ai-repeated-items.js +5 -2
  78. package/src/lib/runtime/ai-repeated-items.js.map +1 -1
  79. package/src/lib/runtime/ai-runtime.js +2 -3
  80. package/src/lib/runtime/ai-runtime.js.map +1 -1
  81. package/src/lib/server/assist-chat.js +8 -3
  82. package/src/lib/server/assist-chat.js.map +1 -1
  83. package/src/lib/server/assist-view-context.d.ts +5 -1
  84. package/src/lib/server/assist-view-context.js +8 -3
  85. package/src/lib/server/assist-view-context.js.map +1 -1
  86. package/src/lib/usage/assist-usage.d.ts +2 -0
  87. package/src/lib/usage/assist-usage.js +8 -3
  88. 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
- /** A line fitted to its place with invented contact details written as gaps. */
61
- export declare function aiLayoutWords(value: unknown, place: AiLayoutTextPlace, facts: string): string;
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
- /** The line with every trailing dangling word taken off, as rule 14 reads one. */ function withoutDanglingTail(line) {
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
- /** A line fitted to its place with invented contact details written as gaps. */ export function aiLayoutWords(value, place, facts) {
110
- return aiLayoutFitText(aiLayoutGuardContacts(aiLayoutCleanText(value), facts), place);
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
- */ export function aiLayoutFitText(value, place) {
116
- const text = fitText(value, place);
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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"}
@@ -29,16 +29,26 @@ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
29
29
  * 1. A stock photo searched for by the slot's own description and copied into
30
30
  * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the
31
31
  * deployment has a stock-photo key.
32
- * 2. Otherwise, or when the search finds nothing fitting, one of the STARTER
33
- * PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`, served at
34
- * `/_static/starter/` by the tenant and the console alike, credited in the
35
- * CREDITS file beside them). They are the platform's own files, so a
36
- * visitor's browser asks no other host for them.
32
+ * 2. Where the library cannot be asked at all — no stock-photo key, the
33
+ * library down, refusing or out of time before it answered — one of the
34
+ * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,
35
+ * served at `/_static/starter/` by the tenant and the console alike,
36
+ * credited in the CREDITS file beside them). They are the platform's own
37
+ * files, so a visitor's browser asks no other host for them, and a
38
+ * starter reads better than no picture when there is no library.
37
39
  *
38
- * A slot keeps the alt text the model wrote unless the photo filling it
39
- * brings its own words: the model wrote it before any photo was found, so a
40
- * stock photo that is not what it says is described by what it is
41
- * (`aiStockPhotoAlt`).
40
+ * Where the library answered and nothing in it fits a slot
41
+ * ({@link AI_LAYOUT_NO_PHOTO}), the slot keeps the compiler's no-photo
42
+ * treatment — its frame with its icon, or its section's own band behind a
43
+ * cover — and never a starter: the beta.257 Copper Kettle Catering start
44
+ * (2026-10-15) opened a barbecue caterer's home page on the starters'
45
+ * pottery workbench (AGL-3831). An empty picture draws nothing on the
46
+ * published page, and the Besigner shows the owner where to choose one.
47
+ *
48
+ * A slot keeps the alt text the model wrote only when it describes the photo
49
+ * placed: a stock photo that is not what it says is described by what it is
50
+ * (`aiStockPhotoAlt`), and a starter photo always by its own words, since
51
+ * the model never wrote for it (AGL-3831).
42
52
  */
43
53
  /** What a picture slot is for, read from where it sits and what it says. */
44
54
  export type AiLayoutPictureRole = 'hero' | 'about' | 'gallery';
@@ -93,34 +103,47 @@ export declare function aiLayoutPictureSlots(nodes: NodesMap, rootId: string, se
93
103
  ids: readonly string[];
94
104
  names: readonly string[];
95
105
  }): AiLayoutPictureSlot[];
96
- /** The starter photos, by name. */
106
+ /** The starter photos, by name, each with the words that describe it. */
97
107
  export declare const AI_LAYOUT_STARTER_PHOTOS: {
98
108
  readonly hero: {
99
109
  readonly src: "/_static/starter/hero-team.jpg";
100
110
  readonly width: 1600;
101
111
  readonly height: 1067;
112
+ readonly alt: "A small team working together around a table in a bright office";
102
113
  };
103
114
  readonly about: {
104
115
  readonly src: "/_static/starter/about-owner.jpg";
105
116
  readonly width: 1600;
106
117
  readonly height: 900;
118
+ readonly alt: "A smiling business owner standing behind the counter of her coffee shop";
107
119
  };
108
120
  readonly desk: {
109
121
  readonly src: "/_static/starter/gallery-desk.jpg";
110
122
  readonly width: 1600;
111
123
  readonly height: 1068;
124
+ readonly alt: "A tidy desk with a laptop, a lamp and fresh flowers";
112
125
  };
113
126
  readonly plants: {
114
127
  readonly src: "/_static/starter/gallery-plants.jpg";
115
128
  readonly width: 1200;
116
129
  readonly height: 900;
130
+ readonly alt: "Houseplants on a sunny windowsill";
117
131
  };
118
132
  readonly craft: {
119
133
  readonly src: "/_static/starter/gallery-craft.jpg";
120
134
  readonly width: 1600;
121
135
  readonly height: 1065;
136
+ readonly alt: "Hands working on a craft project at a workbench";
122
137
  };
123
138
  };
139
+ /**
140
+ * A source's answer for a slot the library answered and nothing in it fits
141
+ * (AGL-3831): the slot keeps its no-photo treatment rather than take a
142
+ * starter. Its source is empty, so a caller reading `src` to fill something
143
+ * that must hold a photo (a product's, a record's) falls back as it would on
144
+ * `null`.
145
+ */
146
+ export declare const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto>;
124
147
  /** A small stable number from a seed, so the same job always picks the same photos. */
125
148
  export declare function aiLayoutSeedNumber(seed: string): number;
126
149
  /**
@@ -148,15 +171,19 @@ export declare function aiLayoutStarterPhotos(slots: readonly AiLayoutPictureSlo
148
171
  export declare function aiLayoutPlacePictures(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[], photos: ReadonlyArray<AiLayoutPicturePhoto | null>): NodesMap;
149
172
  /**
150
173
  * A source of photos found for the slots themselves — a stock search copied
151
- * into the site's library — answering one photo or `null` per slot, in order.
152
- * It may throw; a slot it leaves unfilled takes a starter photo.
174
+ * into the site's library — answering per slot, in order: a photo; or
175
+ * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,
176
+ * which leaves the slot without a photo; or `null` where the library could
177
+ * not be asked, which takes a starter photo. It may throw, and then every
178
+ * slot takes a starter.
153
179
  */
154
180
  export type AiLayoutPictureSource = (slots: readonly AiLayoutPictureSlot[]) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>;
155
181
  /**
156
182
  * Fills a stored page's empty picture slots (AGL-3660): from `source` where
157
- * one is given and it answers, else from the starter photos. Never throws,
158
- * and never leaves a page worse than it came: a picture is decoration, and a
159
- * job never fails over one.
183
+ * one is given and it answers; a slot it answers with nothing fitting stays
184
+ * without a photo; every other slot takes a starter photo, described by its
185
+ * own words (AGL-3831). Never throws, and never leaves a page worse than it
186
+ * came: a picture is decoration, and a job never fails over one.
160
187
  */
161
188
  export declare function aiResolveLayoutPictures(nodes: NodesMap, input: {
162
189
  rootId: string;
@@ -165,4 +192,6 @@ export declare function aiResolveLayoutPictures(nodes: NodesMap, input: {
165
192
  /** A per-job word, so two sites do not open with the same photo. */
166
193
  seed: string;
167
194
  source?: AiLayoutPictureSource | null;
195
+ /** The site's language (AGL-3750); a starter photo's English words are not its own. */
196
+ language?: string;
168
197
  }): Promise<NodesMap>;