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

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 (173) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +7 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +21 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  13. package/src/lib/components/ai-site-start-card.component.js +98 -23
  14. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  16. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  17. package/src/lib/declarations.server.js +12 -0
  18. package/src/lib/declarations.server.js.map +1 -1
  19. package/src/lib/jobs/ai-build-capabilities.js +216 -2
  20. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.d.ts +3 -1
  22. package/src/lib/jobs/ai-job-build-step.js +124 -18
  23. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  25. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  26. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  27. package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
  28. package/src/lib/jobs/ai-job-form-dataset.js +28 -10
  29. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  30. package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
  31. package/src/lib/jobs/ai-job-form-step.js +98 -28
  32. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  33. package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
  34. package/src/lib/jobs/ai-job-layout-language.js +29 -5
  35. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  36. package/src/lib/jobs/ai-job-layout-step.js +4 -1
  37. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
  39. package/src/lib/jobs/ai-job-media-step.js +610 -0
  40. package/src/lib/jobs/ai-job-media-step.js.map +1 -0
  41. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  42. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  43. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  44. package/src/lib/jobs/ai-job-page-language.d.ts +4 -1
  45. package/src/lib/jobs/ai-job-page-language.js +30 -5
  46. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-page-sections.js +5 -4
  48. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-step.js +3 -2
  50. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  51. package/src/lib/jobs/ai-job-plan-step.d.ts +14 -3
  52. package/src/lib/jobs/ai-job-plan-step.js +87 -20
  53. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  55. package/src/lib/jobs/ai-job-products-step.js +2 -2
  56. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-settings-step.d.ts +268 -0
  58. package/src/lib/jobs/ai-job-settings-step.js +690 -0
  59. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  60. package/src/lib/jobs/ai-job-site-content.d.ts +22 -8
  61. package/src/lib/jobs/ai-job-site-content.js +33 -15
  62. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-datasets.d.ts +3 -1
  64. package/src/lib/jobs/ai-job-site-datasets.js +13 -5
  65. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  66. package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
  67. package/src/lib/jobs/ai-job-site-events.js +313 -0
  68. package/src/lib/jobs/ai-job-site-events.js.map +1 -0
  69. package/src/lib/jobs/ai-job-site-step.d.ts +12 -4
  70. package/src/lib/jobs/ai-job-site-step.js +132 -26
  71. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  72. package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
  73. package/src/lib/jobs/ai-job-tracking-step.js +501 -0
  74. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
  75. package/src/lib/jobs/ai-layout-stock-photos.d.ts +21 -0
  76. package/src/lib/jobs/ai-layout-stock-photos.js +41 -4
  77. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  78. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  79. package/src/lib/jobs/ai-site-publish.js +202 -56
  80. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  81. package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
  82. package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
  83. package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
  84. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
  85. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
  86. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
  87. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
  90. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -7
  91. package/src/lib/layout-language/ai-layout-compiler.js +62 -42
  92. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  93. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  94. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  95. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  96. package/src/lib/layout-language/ai-layout-frame.d.ts +24 -5
  97. package/src/lib/layout-language/ai-layout-frame.js +93 -20
  98. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  99. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
  102. package/src/lib/layout-language/ai-layout-labels.js +162 -0
  103. package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
  104. package/src/lib/layout-language/ai-layout-links.js +5 -2
  105. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  106. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  107. package/src/lib/layout-language/ai-layout-listings.js +66 -5
  108. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  109. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  110. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  111. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  112. package/src/lib/model/ai-build-event-page.d.ts +70 -0
  113. package/src/lib/model/ai-build-event-page.js +145 -0
  114. package/src/lib/model/ai-build-event-page.js.map +1 -0
  115. package/src/lib/model/ai-build-job.d.ts +44 -0
  116. package/src/lib/model/ai-build-job.js +141 -13
  117. package/src/lib/model/ai-build-job.js.map +1 -1
  118. package/src/lib/model/ai-build-plan.d.ts +39 -3
  119. package/src/lib/model/ai-build-plan.js +59 -5
  120. package/src/lib/model/ai-build-plan.js.map +1 -1
  121. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  122. package/src/lib/model/ai-credit-estimate.js +51 -0
  123. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  124. package/src/lib/model/ai-job-activity.js +6 -1
  125. package/src/lib/model/ai-job-activity.js.map +1 -1
  126. package/src/lib/model/ai-jobs.types.d.ts +18 -2
  127. package/src/lib/model/ai-jobs.types.js +6 -1
  128. package/src/lib/model/ai-jobs.types.js.map +1 -1
  129. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  130. package/src/lib/model/ai-layout-menus.js +518 -0
  131. package/src/lib/model/ai-layout-menus.js.map +1 -0
  132. package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
  133. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  134. package/src/lib/model/ai-products.d.ts +31 -1
  135. package/src/lib/model/ai-products.js +34 -1
  136. package/src/lib/model/ai-products.js.map +1 -1
  137. package/src/lib/model/ai-site-inventory.d.ts +17 -0
  138. package/src/lib/model/ai-site-inventory.js +5 -0
  139. package/src/lib/model/ai-site-inventory.js.map +1 -1
  140. package/src/lib/model/ai-site-job.d.ts +12 -2
  141. package/src/lib/model/ai-site-job.js +39 -12
  142. package/src/lib/model/ai-site-job.js.map +1 -1
  143. package/src/lib/model/ai-site-store-pages.js +1 -1
  144. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  145. package/src/lib/runtime/ai-doctrine-validators.d.ts +78 -3
  146. package/src/lib/runtime/ai-doctrine-validators.js +219 -31
  147. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  148. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  149. package/src/lib/runtime/ai-doctrine.js +16 -7
  150. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  151. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  152. package/src/lib/runtime/ai-eval-live.js +1 -1
  153. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  154. package/src/lib/runtime/ai-palette.d.ts +3 -1
  155. package/src/lib/runtime/ai-palette.generated.js +499 -11
  156. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  157. package/src/lib/runtime/ai-palette.js +5 -3
  158. package/src/lib/runtime/ai-palette.js.map +1 -1
  159. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  160. package/src/lib/runtime/ai-products-generation.js +12 -6
  161. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  162. package/src/lib/runtime/ai-template-examples.js +8 -2
  163. package/src/lib/runtime/ai-template-examples.js.map +1 -1
  164. package/src/lib/runtime/site-inventory.d.ts +12 -0
  165. package/src/lib/runtime/site-inventory.js +29 -1
  166. package/src/lib/runtime/site-inventory.js.map +1 -1
  167. package/src/lib/server.js +20 -0
  168. package/src/lib/server.js.map +1 -1
  169. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  170. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  171. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  172. package/src/lib/tools/ai-products-tool.js +15 -5
  173. package/src/lib/tools/ai-products-tool.js.map +1 -1
@@ -73,6 +73,22 @@ export interface AiSitePublishDeps {
73
73
  * door). Every other build leaves drafts.
74
74
  */
75
75
  export declare function aiJobPublishesBuild(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
76
+ /**
77
+ * The host fields a guided start writes for its plan's language (AGL-3750),
78
+ * as the Languages card saves one language: `locales` of that one and
79
+ * `defaultLocale` the same. `null` — nothing written — where the plan said no
80
+ * valid language, where it is the language the site already publishes in
81
+ * (English for a new site), and on a site with more than one language, whose
82
+ * languages are the owner's decision. Nothing is translated: the pages were
83
+ * written in the brief's language, and this says so to the reader.
84
+ */
85
+ export declare function aiSiteLanguageUpdates(host: {
86
+ locales?: unknown;
87
+ defaultLocale?: unknown;
88
+ }, language: string | null | undefined): {
89
+ locales: string[];
90
+ defaultLocale: string;
91
+ } | null;
76
92
  /** Whether a finished job of this shape publishes what it built. */
77
93
  export declare function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
78
94
  /**
@@ -103,10 +119,12 @@ interface NavEntry {
103
119
  export declare function aiSiteUnwrittenBlogHrefs(blogUnwritten: boolean | undefined): string[];
104
120
  /**
105
121
  * The layout's nodes with its links to the retired starter home pointed at
106
- * the new home, and a link for each proposed entry the header does not
107
- * already carry, placed in the header's toolbar after its existing links.
108
- * `null` when nothing changes, or when the layout has no toolbar to hold an
109
- * entry and no link to repoint.
122
+ * the new home, its links to pages the start did not write taken out, and a
123
+ * link for each proposed entry the header does not already carry, added by
124
+ * the navigation op's `aiLayoutWithMenuChanges` (AGL-3747): to the header's
125
+ * menu and its phone menu, after their items, or after the brand in a
126
+ * starter's Toolbar. `null` when nothing changes, or when the layout has no
127
+ * header to hold an entry and no link to repoint or take out.
110
128
  */
111
129
  export declare function aiLayoutWithNavigation(nodes: NodesMap, input: {
112
130
  entries: readonly NavEntry[];
@@ -115,6 +133,41 @@ export declare function aiLayoutWithNavigation(nodes: NodesMap, input: {
115
133
  /** Paths whose links come out: a blog the start linked and never wrote (AGL-3660). */
116
134
  droppedHrefs?: readonly string[];
117
135
  }): NodesMap | null;
136
+ /** The blog a start writes, as its header entry links it (`aiSiteBlogNavPage`). */
137
+ export interface AiSiteBlogEntry {
138
+ label: string;
139
+ href: string;
140
+ }
141
+ /**
142
+ * The layout's nodes with the blog's header entry put back (AGL-3747): a
143
+ * start whose posts part failed published with every link to the blog taken
144
+ * out (AGL-3660), and Try again's posts went live with none to reach them.
145
+ * Added by the navigation op's `aiLayoutWithMenuChanges`, as the start's
146
+ * other entries are — the bar's menu and the phone menu — second after the
147
+ * home page's item, as the start first linked it (AGL-3676), else last.
148
+ * `null` where some link already opens the blog, or the layout has no header
149
+ * menu to hold it.
150
+ */
151
+ export declare function aiLayoutWithBlogEntry(nodes: NodesMap, input: {
152
+ blog: AiSiteBlogEntry;
153
+ homeId?: string | null;
154
+ }): NodesMap | null;
155
+ /**
156
+ * Puts the blog's header entry back on a live start whose posts Try again
157
+ * wrote (AGL-3747), as a new live version of the layout its published pages
158
+ * render inside, in one batch with the publish outbox entry that drops the
159
+ * live site's cached pages, the drop itself attempted at once. Returns
160
+ * whether the entry went live; never throws for a layout it cannot change.
161
+ */
162
+ export declare function aiRestoreGuidedBlogEntry(firestore: Firestore, input: {
163
+ job: Pick<AiJob, '$id' | 'hostId'>;
164
+ /** The start's published pages: the layout they share is the one changed. */
165
+ pageIds: readonly string[];
166
+ /** The published home page, which the blog's entry follows. */
167
+ homeId?: string | null;
168
+ blog: AiSiteBlogEntry;
169
+ now: Date;
170
+ }, deps?: Pick<AiSitePublishDeps, 'dropCache'>): Promise<boolean>;
118
171
  /**
119
172
  * A retry's publish merged into the start's first (AGL-3730): the pages it
120
173
  * put live join the published list, a page it published leaves the drafts,
@@ -144,5 +197,10 @@ export declare function aiPublishGuidedSite(firestore: Firestore, input: {
144
197
  * starter still holds stays a draft and says so.
145
198
  */
146
199
  retry?: boolean;
200
+ /**
201
+ * The language the plan says the brief and copy are in (AGL-3750): the
202
+ * site publishes in it, where `aiSiteLanguageUpdates` says so.
203
+ */
204
+ language?: string | null;
147
205
  }, deps?: AiSitePublishDeps): Promise<AiJobSitePublish>;
148
206
  export {};
@@ -18,12 +18,15 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
19
19
  import { hostPublicOrigin } from "@aglyn/aglyn/app-utils/host-naming";
20
20
  import { blockingRouteOwner, normalizeScreenSlug, SCREEN_ROOT_PATH, screenRoutePathToUrl } from "@aglyn/aglyn/app-utils/screen-route";
21
+ import { resolvePageLocale, siteLanguageCode } from "@aglyn/aglyn/app-utils/seo-locale";
21
22
  import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
22
23
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
23
24
  import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
24
25
  import { FieldValue } from "firebase-admin/firestore";
26
+ import { aiLayoutFrameHollows, aiLayoutFrameHollowsSaid, aiLayoutFrameIsHollow, aiLayoutRepairFrameHollows, aiLayoutWithoutFrameHollows } from "../layout-language/ai-layout-frame-hollows.js";
25
27
  import { AI_SITE_BLOG_SLUGS } from "../model/ai-site-job.js";
26
28
  import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
29
+ import { aiLayoutWithMenuChanges, aiNoMenuChanges } from "../model/ai-layout-menus.js";
27
30
  import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-job-store-links-layout.js";
28
31
  /**
29
32
  * Whether a build puts its pages live (AGL-3616): only where its request
@@ -34,6 +37,34 @@ import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-jo
34
37
  var _job_inputs, _job_inputs1;
35
38
  return job.kind === 'build' && ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['publish']) === true && ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['publishConfirmed']) === true;
36
39
  }
40
+ /**
41
+ * The host fields a guided start writes for its plan's language (AGL-3750),
42
+ * as the Languages card saves one language: `locales` of that one and
43
+ * `defaultLocale` the same. `null` — nothing written — where the plan said no
44
+ * valid language, where it is the language the site already publishes in
45
+ * (English for a new site), and on a site with more than one language, whose
46
+ * languages are the owner's decision. Nothing is translated: the pages were
47
+ * written in the brief's language, and this says so to the reader.
48
+ */ export function aiSiteLanguageUpdates(host, language) {
49
+ const code = siteLanguageCode(language);
50
+ if (!code) return null;
51
+ const locales = Array.isArray(host.locales) ? host.locales.filter((one)=>typeof one === 'string' && one.trim() !== '') : [];
52
+ if (locales.length > 1) return null;
53
+ const current = resolvePageLocale({
54
+ host: _extends({}, typeof host.defaultLocale === 'string' ? {
55
+ defaultLocale: host.defaultLocale
56
+ } : {}, {
57
+ locales
58
+ })
59
+ });
60
+ if (current === code) return null;
61
+ return {
62
+ locales: [
63
+ code
64
+ ],
65
+ defaultLocale: code
66
+ };
67
+ }
37
68
  /** Whether a finished job of this shape publishes what it built. */ export function aiJobPublishesSite(job) {
38
69
  var _job_inputs;
39
70
  return job.kind === 'site' && ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['autoConfirm']) === true;
@@ -80,10 +111,12 @@ function navEntriesOf(outputs) {
80
111
  }
81
112
  /**
82
113
  * The layout's nodes with its links to the retired starter home pointed at
83
- * the new home, and a link for each proposed entry the header does not
84
- * already carry, placed in the header's toolbar after its existing links.
85
- * `null` when nothing changes, or when the layout has no toolbar to hold an
86
- * entry and no link to repoint.
114
+ * the new home, its links to pages the start did not write taken out, and a
115
+ * link for each proposed entry the header does not already carry, added by
116
+ * the navigation op's `aiLayoutWithMenuChanges` (AGL-3747): to the header's
117
+ * menu and its phone menu, after their items, or after the brand in a
118
+ * starter's Toolbar. `null` when nothing changes, or when the layout has no
119
+ * header to hold an entry and no link to repoint or take out.
87
120
  */ export function aiLayoutWithNavigation(nodes, input) {
88
121
  var _input_droppedHrefs;
89
122
  const map = {};
@@ -113,6 +146,9 @@ function navEntriesOf(outputs) {
113
146
  var _node_nodes;
114
147
  if ((_node_nodes = node.nodes) == null ? void 0 : _node_nodes.some((child)=>gone.has(child))) node.nodes = node.nodes.filter((child)=>!gone.has(child));
115
148
  }
149
+ // A footer heading whose links all went goes with them, and its column
150
+ // when that leaves it only the heading (AGL-3735).
151
+ aiLayoutRepairFrameHollows(map);
116
152
  changed = true;
117
153
  }
118
154
  }
@@ -128,51 +164,140 @@ function navEntriesOf(outputs) {
128
164
  }
129
165
  }
130
166
  }
167
+ // The entries go into the header through the navigation op's own code
168
+ // (AGL-3747): the bar's menu and the phone menu's, after the items already
169
+ // there, each looking like its neighbours. A page some link already opens,
170
+ // or an item of the same name, is not added again.
131
171
  const linked = new Set(links.map((link)=>{
132
172
  var _link_props;
133
173
  return (_link_props = link.props) == null ? void 0 : _link_props['screenId'];
134
174
  }).filter(Boolean));
135
- const toolbar = Object.values(map).find((node)=>node.componentId === 'muiToolbar');
136
175
  const missing = input.entries.filter((entry)=>!linked.has(entry.screenId));
137
- if (toolbar && missing.length) {
138
- var _toolbar_nodes;
139
- const children = (_toolbar_nodes = toolbar.nodes) != null ? _toolbar_nodes : [];
140
- // After the last link already in the toolbar (the brand), before the
141
- // search, the mode switch and the call to action.
142
- const lastLink = children.reduce((at, id, index)=>{
143
- var _map_id;
144
- return ((_map_id = map[id]) == null ? void 0 : _map_id.componentId) === 'muiScreenLink' ? index : at;
145
- }, -1);
146
- const added = missing.map((entry)=>{
147
- const id = `ai_nav_${createResourceUid()}`;
148
- map[id] = {
149
- $id: id,
150
- componentId: 'muiScreenLink',
151
- pluginId: 'mui',
152
- parentId: toolbar.$id,
153
- props: {
154
- children: entry.label,
155
- screenId: entry.screenId,
156
- renderAs: 'link',
157
- color: 'inherit'
158
- },
159
- sx: {
160
- textDecoration: 'none',
161
- whiteSpace: 'nowrap'
162
- },
163
- nodes: []
164
- };
165
- return id;
176
+ if (missing.length) {
177
+ const added = aiLayoutWithMenuChanges(map, [
178
+ 'header'
179
+ ], _extends({}, aiNoMenuChanges(), {
180
+ add: missing.map((entry)=>({
181
+ parent: null,
182
+ label: entry.label,
183
+ to: {
184
+ screenId: entry.screenId
185
+ }
186
+ }))
187
+ }), {
188
+ newId: ()=>`ai_nav_${createResourceUid()}`
166
189
  });
167
- toolbar.nodes = [
168
- ...children.slice(0, lastLink + 1),
169
- ...added,
170
- ...children.slice(lastLink + 1)
171
- ];
172
- changed = true;
190
+ if (added.nodes) return added.nodes;
173
191
  }
174
192
  return changed ? map : null;
175
193
  }
194
+ /**
195
+ * The layout's nodes with the blog's header entry put back (AGL-3747): a
196
+ * start whose posts part failed published with every link to the blog taken
197
+ * out (AGL-3660), and Try again's posts went live with none to reach them.
198
+ * Added by the navigation op's `aiLayoutWithMenuChanges`, as the start's
199
+ * other entries are — the bar's menu and the phone menu — second after the
200
+ * home page's item, as the start first linked it (AGL-3676), else last.
201
+ * `null` where some link already opens the blog, or the layout has no header
202
+ * menu to hold it.
203
+ */ export function aiLayoutWithBlogEntry(nodes, input) {
204
+ var _added_nodes;
205
+ const blogHrefs = new Set([
206
+ input.blog.href,
207
+ ...AI_SITE_BLOG_SLUGS.map((slug)=>`/${slug}`)
208
+ ]);
209
+ const linked = Object.values(nodes).some((node)=>{
210
+ var _ref;
211
+ var _node_props;
212
+ return (node == null ? void 0 : node.componentId) === 'muiScreenLink' && blogHrefs.has(String((_ref = (_node_props = node.props) == null ? void 0 : _node_props['href']) != null ? _ref : ''));
213
+ });
214
+ if (linked) return null;
215
+ const home = [
216
+ ...input.homeId ? [
217
+ {
218
+ screenId: input.homeId
219
+ }
220
+ ] : [],
221
+ {
222
+ href: '/'
223
+ }
224
+ ];
225
+ const added = aiLayoutWithMenuChanges(nodes, [
226
+ 'header'
227
+ ], _extends({}, aiNoMenuChanges(), {
228
+ add: [
229
+ {
230
+ parent: null,
231
+ label: input.blog.label,
232
+ to: {
233
+ href: input.blog.href
234
+ },
235
+ after: home
236
+ }
237
+ ]
238
+ }), {
239
+ newId: ()=>`ai_nav_${createResourceUid()}`
240
+ });
241
+ return (_added_nodes = added.nodes) != null ? _added_nodes : null;
242
+ }
243
+ /**
244
+ * Puts the blog's header entry back on a live start whose posts Try again
245
+ * wrote (AGL-3747), as a new live version of the layout its published pages
246
+ * render inside, in one batch with the publish outbox entry that drops the
247
+ * live site's cached pages, the drop itself attempted at once. Returns
248
+ * whether the entry went live; never throws for a layout it cannot change.
249
+ */ export async function aiRestoreGuidedBlogEntry(firestore, input, deps = {}) {
250
+ var _deps_dropCache, _input_job_hostId;
251
+ const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
252
+ const hostId = (_input_job_hostId = input.job.hostId) != null ? _input_job_hostId : '';
253
+ if (!hostId || !input.pageIds.length) return false;
254
+ const layoutId = await aiPagesLayoutId(firestore, hostId, input.pageIds);
255
+ if (!layoutId) return false;
256
+ const write = await aiLayoutVersionWrite(firestore, {
257
+ hostId,
258
+ layoutId,
259
+ transform: (nodes)=>{
260
+ var _input_homeId;
261
+ return aiLayoutWithBlogEntry(nodes, {
262
+ blog: input.blog,
263
+ homeId: (_input_homeId = input.homeId) != null ? _input_homeId : null
264
+ });
265
+ },
266
+ storeLinks: null,
267
+ jobId: input.job.$id,
268
+ now: input.now,
269
+ displayName: 'Navigation for the blog'
270
+ });
271
+ if (!(write == null ? void 0 : write.version)) return false;
272
+ const batch = firestore.batch();
273
+ batch.create(write.versionRef, write.version);
274
+ batch.set(write.layoutRef, {
275
+ versionId: write.versionRef.id,
276
+ updatedAt: input.now
277
+ }, {
278
+ merge: true
279
+ });
280
+ // A layout change reaches every page, so the announce asks for the whole site.
281
+ const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid());
282
+ batch.set(outboxRef, {
283
+ hostId,
284
+ paths: [
285
+ SCREEN_ROOT_PATH
286
+ ],
287
+ createdAt: FieldValue.serverTimestamp(),
288
+ attempts: 0,
289
+ entireHost: true
290
+ });
291
+ await batch.commit();
292
+ const dropped = await dropCache({
293
+ hostIds: [
294
+ hostId
295
+ ],
296
+ reason: 'guided AI site start linked its blog'
297
+ });
298
+ if (dropped.complete) await outboxRef.delete().catch(()=>undefined);
299
+ return true;
300
+ }
176
301
  /**
177
302
  * A retry's publish merged into the start's first (AGL-3730): the pages it
178
303
  * put live join the published list, a page it published leaves the drafts,
@@ -342,6 +467,14 @@ function navEntriesOf(outputs) {
342
467
  } else if (placeholder && entries[placeholder]) {
343
468
  hostUpdates['defaultHomeScreenId'] = FieldValue.delete();
344
469
  }
470
+ // The site's language, from its plan (AGL-3750): every page's `<html lang>`
471
+ // reads it, so the announce below asks for the whole site.
472
+ const language = aiSiteLanguageUpdates({
473
+ locales: host.get('locales'),
474
+ defaultLocale: host.get('defaultLocale')
475
+ }, input.language);
476
+ if (language) Object.assign(hostUpdates, language);
477
+ const entireHost = layoutWrites.length > 0 || language !== null;
345
478
  batch.update(hostRef, hostUpdates);
346
479
  for (const page of accepted){
347
480
  batch.set(hostRef.collection('screens').doc(page.id), {
@@ -370,7 +503,7 @@ function navEntriesOf(outputs) {
370
503
  ],
371
504
  createdAt: FieldValue.serverTimestamp(),
372
505
  attempts: 0
373
- }, layoutWrites.length ? {
506
+ }, entireHost ? {
374
507
  entireHost: true
375
508
  } : {}));
376
509
  try {
@@ -400,7 +533,7 @@ function navEntriesOf(outputs) {
400
533
  hostId
401
534
  ],
402
535
  reason: 'guided AI site start published'
403
- }, layoutWrites.length ? {} : {
536
+ }, entireHost ? {} : {
404
537
  paths: {
405
538
  [hostId]: paths
406
539
  }
@@ -421,9 +554,21 @@ function navEntriesOf(outputs) {
421
554
  */ async function aiNavigationLayoutWrite(firestore, input) {
422
555
  var _input_storeLinks;
423
556
  if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.storeLinks) return null;
424
- const hostRef = firestore.collection('hosts').doc(input.hostId);
557
+ const layoutId = await aiPagesLayoutId(firestore, input.hostId, input.pageIds);
558
+ if (!layoutId) return null;
559
+ return aiLayoutVersionWrite(firestore, {
560
+ hostId: input.hostId,
561
+ layoutId,
562
+ transform: (nodes)=>aiLayoutWithNavigation(nodes, input),
563
+ storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null,
564
+ jobId: input.jobId,
565
+ now: input.now
566
+ });
567
+ }
568
+ /** The one layout the pages render inside, by their current versions; `null` where they do not share one. */ async function aiPagesLayoutId(firestore, hostId, pageIds) {
569
+ const hostRef = firestore.collection('hosts').doc(hostId);
425
570
  const layoutIds = new Set();
426
- for (const id of input.pageIds){
571
+ for (const id of pageIds){
427
572
  var _ref;
428
573
  const screen = await hostRef.collection('screens').doc(id).get();
429
574
  const versionId = screen.get('versionId');
@@ -431,18 +576,9 @@ function navEntriesOf(outputs) {
431
576
  const layoutId = (_ref = version == null ? void 0 : version.get('layoutId')) != null ? _ref : screen.get('layoutId');
432
577
  if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId);
433
578
  }
434
- if (layoutIds.size !== 1) return null;
435
- const [layoutId] = [
579
+ return layoutIds.size === 1 ? [
436
580
  ...layoutIds
437
- ];
438
- return aiLayoutVersionWrite(firestore, {
439
- hostId: input.hostId,
440
- layoutId,
441
- transform: (nodes)=>aiLayoutWithNavigation(nodes, input),
442
- storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null,
443
- jobId: input.jobId,
444
- now: input.now
445
- });
581
+ ][0] : null;
446
582
  }
447
583
  /**
448
584
  * The new live version of one layout: `transform` applied to the version
@@ -451,6 +587,7 @@ function navEntriesOf(outputs) {
451
587
  * to the live version otherwise; a draft with nothing more to add becomes
452
588
  * the live version as it is. `null` when nothing changes.
453
589
  */ async function aiLayoutVersionWrite(firestore, input) {
590
+ var _aiLayoutWithoutFrameHollows, _input_displayName;
454
591
  var _input_storeLinks;
455
592
  const { layoutId } = input;
456
593
  const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(layoutId);
@@ -471,6 +608,15 @@ function navEntriesOf(outputs) {
471
608
  const added = aiLayoutWithStoreLinks(next != null ? next : nodes, links.links, links.screenPaths);
472
609
  next = (_added_nodes = added.nodes) != null ? _added_nodes : next;
473
610
  }
611
+ // What a publish puts live has no hollow heading, column or menu (AGL-3735).
612
+ next = (_aiLayoutWithoutFrameHollows = aiLayoutWithoutFrameHollows(next != null ? next : nodes)) != null ? _aiLayoutWithoutFrameHollows : next;
613
+ const hollow = aiLayoutFrameHollows(next != null ? next : nodes);
614
+ if (aiLayoutFrameIsHollow(hollow)) {
615
+ console.error('ai site publish: a layout is still hollow after its repair', {
616
+ layoutId,
617
+ hollow: aiLayoutFrameHollowsSaid(hollow)
618
+ });
619
+ }
474
620
  if (!next) return fromDraft && draftRef ? {
475
621
  layoutRef,
476
622
  versionRef: draftRef,
@@ -485,7 +631,7 @@ function navEntriesOf(outputs) {
485
631
  version: {
486
632
  layoutId,
487
633
  hostId: input.hostId,
488
- displayName: 'Navigation for the new pages',
634
+ displayName: (_input_displayName = input.displayName) != null ? _input_displayName : 'Navigation for the new pages',
489
635
  nodes: Buffer.from(packed),
490
636
  createdAt: input.now,
491
637
  updatedAt: input.now,