@aglyn/plugins-ai 1.0.0-beta.240 → 1.0.0-beta.242

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 (92) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +17 -0
  3. package/src/lib/components/ai-job-links.js +63 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-job-plan.component.js +8 -1
  6. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  7. package/src/lib/components/ai-site-build-page.component.js +7 -1
  8. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  9. package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
  10. package/src/lib/components/ai-store-finish-card.component.js +116 -0
  11. package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
  12. package/src/lib/components/assist-build-card.component.js +12 -1
  13. package/src/lib/components/assist-build-card.component.js.map +1 -1
  14. package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
  15. package/src/lib/jobs/ai-job-build-step.js +283 -7
  16. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
  18. package/src/lib/jobs/ai-job-drafts.js +4 -1
  19. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  21. package/src/lib/jobs/ai-job-layout-language.js +4 -2
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +3 -1
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  26. package/src/lib/jobs/ai-job-page-language.js +19 -7
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
  29. package/src/lib/jobs/ai-job-page-sections.js +40 -3
  30. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  31. package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
  32. package/src/lib/jobs/ai-job-page-step.js +19 -8
  33. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
  35. package/src/lib/jobs/ai-job-plan-step.js +41 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.js +3 -2
  38. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  39. package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
  40. package/src/lib/jobs/ai-job-site-datasets.js +206 -13
  41. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  42. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  43. package/src/lib/jobs/ai-job-site-step.js +109 -22
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  46. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  47. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  48. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  49. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  50. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  51. package/src/lib/jobs/ai-layout-stock-photos.d.ts +129 -8
  52. package/src/lib/jobs/ai-layout-stock-photos.js +919 -75
  53. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  54. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  55. package/src/lib/jobs/ai-site-publish.js +76 -15
  56. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  57. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  58. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  59. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  60. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.d.ts +39 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js +508 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  64. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  65. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  66. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  67. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  68. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  69. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  70. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  71. package/src/lib/model/ai-build-job.d.ts +2 -0
  72. package/src/lib/model/ai-build-job.js +23 -3
  73. package/src/lib/model/ai-build-job.js.map +1 -1
  74. package/src/lib/model/ai-build-plan.d.ts +7 -0
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-build-progress.js +3 -1
  77. package/src/lib/model/ai-build-progress.js.map +1 -1
  78. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  79. package/src/lib/model/ai-build-store-pages.js +209 -0
  80. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  81. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  82. package/src/lib/model/ai-layout-store-links.js +165 -0
  83. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  84. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  85. package/src/lib/model/ai-site-inventory.js.map +1 -1
  86. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  87. package/src/lib/model/ai-site-store-pages.js +383 -0
  88. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  89. package/src/lib/runtime/site-inventory.js +8 -3
  90. package/src/lib/runtime/site-inventory.js.map +1 -1
  91. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  92. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -23,6 +23,8 @@ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cac
23
23
  import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
24
24
  import { FieldValue } from "firebase-admin/firestore";
25
25
  import { AI_SITE_BLOG_SLUGS } from "../model/ai-site-job.js";
26
+ import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
27
+ import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-job-store-links-layout.js";
26
28
  /**
27
29
  * Whether a build puts its pages live (AGL-3616): only where its request
28
30
  * asked to (`publish`, set by Assist's proposal) AND the member ticked the
@@ -176,7 +178,7 @@ function navEntriesOf(outputs) {
176
178
  * Firestore failure of the publish write itself reports every page as a
177
179
  * draft, for the same reason, and is logged.
178
180
  */ export async function aiPublishGuidedSite(firestore, input, deps = {}) {
179
- var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _ref;
181
+ var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1;
180
182
  const sendSitePublished = (_deps_sendSitePublished = deps.sendSitePublished) != null ? _deps_sendSitePublished : sendGa4SitePublished;
181
183
  const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
182
184
  const { job, now } = input;
@@ -252,11 +254,15 @@ function navEntriesOf(outputs) {
252
254
  hostId,
253
255
  pageIds: accepted.map((page)=>page.id),
254
256
  entries: navEntriesOf(pages).filter((entry)=>entries[entry.screenId]),
255
- droppedHrefs: aiSiteUnwrittenBlogHrefs(input.blogUnwritten),
257
+ droppedHrefs: [
258
+ ...aiSiteUnwrittenBlogHrefs(input.blogUnwritten),
259
+ ...(_input_unwrittenHrefs = input.unwrittenHrefs) != null ? _input_unwrittenHrefs : []
260
+ ],
256
261
  retiredHomeId: releases,
257
262
  homeId: (_ref = home == null ? void 0 : home.id) != null ? _ref : null,
258
263
  jobId: job.$id,
259
- now
264
+ now,
265
+ storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null
260
266
  }).catch((error)=>{
261
267
  console.error('ai site publish: navigation not applied', {
262
268
  hostId,
@@ -265,6 +271,27 @@ function navEntriesOf(outputs) {
265
271
  });
266
272
  return null;
267
273
  });
274
+ // A store's links drafted into a layout the pages' navigation did not touch go live on their own.
275
+ const storeLinks = (_input_storeLinks1 = input.storeLinks) != null ? _input_storeLinks1 : null;
276
+ const storeLinksWrite = storeLinks && (layoutWrite == null ? void 0 : layoutWrite.layoutRef.id) !== storeLinks.layoutId ? await aiLayoutVersionWrite(firestore, {
277
+ hostId,
278
+ layoutId: storeLinks.layoutId,
279
+ transform: ()=>null,
280
+ storeLinks,
281
+ jobId: job.$id,
282
+ now
283
+ }).catch((error)=>{
284
+ console.error('ai site publish: store links not applied', {
285
+ hostId,
286
+ jobId: job.$id,
287
+ error
288
+ });
289
+ return null;
290
+ }) : null;
291
+ const layoutWrites = [
292
+ layoutWrite,
293
+ storeLinksWrite
294
+ ].filter((write)=>Boolean(write));
268
295
  const paths = [
269
296
  ...new Set([
270
297
  ...Object.values(entries),
@@ -300,10 +327,10 @@ function navEntriesOf(outputs) {
300
327
  merge: true
301
328
  });
302
329
  }
303
- if (layoutWrite) {
304
- batch.create(layoutWrite.versionRef, layoutWrite.version);
305
- batch.set(layoutWrite.layoutRef, {
306
- versionId: layoutWrite.versionRef.id,
330
+ for (const write of layoutWrites){
331
+ if (write.version) batch.create(write.versionRef, write.version);
332
+ batch.set(write.layoutRef, {
333
+ versionId: write.versionRef.id,
307
334
  updatedAt: now
308
335
  }, {
309
336
  merge: true
@@ -319,7 +346,7 @@ function navEntriesOf(outputs) {
319
346
  ],
320
347
  createdAt: FieldValue.serverTimestamp(),
321
348
  attempts: 0
322
- }, layoutWrite ? {
349
+ }, layoutWrites.length ? {
323
350
  entireHost: true
324
351
  } : {}));
325
352
  try {
@@ -349,7 +376,7 @@ function navEntriesOf(outputs) {
349
376
  hostId
350
377
  ],
351
378
  reason: 'guided AI site start published'
352
- }, layoutWrite ? {} : {
379
+ }, layoutWrites.length ? {} : {
353
380
  paths: {
354
381
  [hostId]: paths
355
382
  }
@@ -367,7 +394,8 @@ function navEntriesOf(outputs) {
367
394
  * their navigation entries and the home link moved off a retired starter;
368
395
  * `null` when there is nothing to change or no one layout to change.
369
396
  */ async function aiNavigationLayoutWrite(firestore, input) {
370
- if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length) return null;
397
+ var _input_storeLinks;
398
+ if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.storeLinks) return null;
371
399
  const hostRef = firestore.collection('hosts').doc(input.hostId);
372
400
  const layoutIds = new Set();
373
401
  for (const id of input.pageIds){
@@ -382,16 +410,49 @@ function navEntriesOf(outputs) {
382
410
  const [layoutId] = [
383
411
  ...layoutIds
384
412
  ];
385
- const layoutRef = hostRef.collection('layouts').doc(layoutId);
413
+ return aiLayoutVersionWrite(firestore, {
414
+ hostId: input.hostId,
415
+ layoutId,
416
+ transform: (nodes)=>aiLayoutWithNavigation(nodes, input),
417
+ storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null,
418
+ jobId: input.jobId,
419
+ now: input.now
420
+ });
421
+ }
422
+ /**
423
+ * The new live version of one layout: `transform` applied to the version
424
+ * live now. A store's links drafted into this layout (AGL-3676) start it from
425
+ * that draft while the draft was made from the live version, and are added
426
+ * to the live version otherwise; a draft with nothing more to add becomes
427
+ * the live version as it is. `null` when nothing changes.
428
+ */ async function aiLayoutVersionWrite(firestore, input) {
429
+ var _input_storeLinks;
430
+ const { layoutId } = input;
431
+ const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(layoutId);
386
432
  const layout = await layoutRef.get();
387
433
  const currentVersionId = layout.get('versionId');
388
434
  if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null;
389
435
  const current = await layoutRef.collection('versions').doc(currentVersionId).get();
390
- const nodes = current.exists ? decodeStoredNodes(current.get('nodes')) : null;
436
+ const links = ((_input_storeLinks = input.storeLinks) == null ? void 0 : _input_storeLinks.layoutId) === layoutId ? input.storeLinks : null;
437
+ const draftRef = links ? layoutRef.collection('versions').doc(links.versionId) : null;
438
+ const draft = draftRef ? await draftRef.get() : null;
439
+ const fromDraft = Boolean((draft == null ? void 0 : draft.exists) && draft.get(STORE_LINKS_SOURCE_FIELD) === currentVersionId);
440
+ const base = fromDraft && draft ? draft : current;
441
+ const nodes = base.exists ? decodeStoredNodes(base.get('nodes')) : null;
391
442
  if (!nodes) return null;
392
- const next = aiLayoutWithNavigation(nodes, input);
393
- const packed = next ? encodeStoredNodes(next) : null;
394
- if (!next || !packed) return null;
443
+ let next = input.transform(nodes);
444
+ if (links && !fromDraft) {
445
+ var _added_nodes;
446
+ const added = aiLayoutWithStoreLinks(next != null ? next : nodes, links.links, links.screenPaths);
447
+ next = (_added_nodes = added.nodes) != null ? _added_nodes : next;
448
+ }
449
+ if (!next) return fromDraft && draftRef ? {
450
+ layoutRef,
451
+ versionRef: draftRef,
452
+ version: null
453
+ } : null;
454
+ const packed = encodeStoredNodes(next);
455
+ if (!packed) return null;
395
456
  const versionRef = layoutRef.collection('versions').doc(createResourceUid());
396
457
  return {
397
458
  layoutRef,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-publish.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\nimport {\n blockingRouteOwner,\n normalizeScreenSlug,\n SCREEN_ROOT_PATH,\n screenRoutePathToUrl,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\nimport { AI_SITE_BLOG_SLUGS } from '../model/ai-site-job'\n\n/**\n * A guided site start publishes what it built (AGL-3596).\n *\n * A person who asked for \"a website\" in the guided start and watched it build\n * was then handed drafts: a site whose address still answered with the\n * starter, or with nothing. A `site` job started from the guided start (its\n * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends\n * by putting every page it built on the site, through the same writes a\n * person's Publish makes:\n *\n * - each page's address in the host's routing map, `publishedAt` on the page,\n * in ONE batch with the publish outbox entry that drops the live site's\n * cached pages (AGL-2575), the drop itself attempted at once;\n * - the home page takes `/` from the starter home page the site was created\n * with (AGL-3408), and an untouched starter goes to the trash rather than\n * staying beside it as a second \"Home\";\n * - the navigation entries the pages proposed are added to the header of\n * the layout they render inside, as a new live version of that layout;\n * - `site_published` once, server-side, as the scheduled publish sends it.\n *\n * A page that cannot be published — deleted meanwhile, no address, an address\n * another page holds — stays a draft and is reported with the plain sentence\n * for why. It never fails the job: the site is still built, and the person\n * fixes that one page in Pages. Every other job kind keeps producing drafts.\n *\n * The allowance is respected by construction: a draft is counted against the\n * plan's pages when it is written (`aiDraftBandRefusal`), so publishing\n * drafts adds none, and retiring the starter frees the one it held.\n */\n\nexport interface AiSitePublishDeps {\n sendSitePublished?: typeof sendGa4SitePublished\n dropCache?: typeof dropPluginSiteCache\n}\n\n/**\n * Whether a build puts its pages live (AGL-3616): only where its request\n * asked to (`publish`, set by Assist's proposal) AND the member ticked the\n * plan card's box when confirming (`publishConfirmed`, set by the resume\n * door). Every other build leaves drafts.\n */\nexport function aiJobPublishesBuild(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'build' && job.inputs?.['publish'] === true && job.inputs?.['publishConfirmed'] === true\n}\n\n/** Whether a finished job of this shape publishes what it built. */\nexport function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && job.inputs?.['autoConfirm'] === true\n}\n\n/**\n * Where a site answers: its custom domain, else its subdomain under the\n * deployment's tenant apex — never a hard-coded platform host, so a\n * self-hosted deployment links to its own sites.\n */\nexport function aiSiteLiveUrl(host: { cname?: unknown; subdomain?: unknown }): string | null {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const origin = hostPublicOrigin({ cname: text(host.cname) || null, subdomain: text(host.subdomain) || null })\n return origin ? `${origin}/` : null\n}\n\n/** The sentences a page that stayed a draft is reported with. */\nexport const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.'\nexport const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.'\nexport const aiSitePublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`\nexport const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.'\n\n/** A navigation entry a page proposed, as the page step reports it. */\ninterface NavEntry {\n screenId: string\n label: string\n}\n\nfunction navEntriesOf(outputs: readonly AiJobOutput[]): NavEntry[] {\n const entries: NavEntry[] = []\n for (const output of outputs) {\n if (output.resource !== 'screen') continue\n const navigation = output.proposal?.['navigation'] as { label?: unknown } | undefined\n const label = typeof navigation?.label === 'string' ? navigation.label.trim() : ''\n if (label && !entries.some((entry) => entry.screenId === output.id)) {\n entries.push({ screenId: output.id, label })\n }\n }\n return entries\n}\n\ntype MutableNode = {\n $id: string\n componentId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\n/**\n * The blog paths a start's layout links and must not publish (AGL-3660): its\n * header, phone menu and footer link the blog by path while the posts part is\n * still owed, so a start whose posts part then failed or was skipped drops\n * every address the blog could have taken. None while the blog was written.\n */\nexport function aiSiteUnwrittenBlogHrefs(blogUnwritten: boolean | undefined): string[] {\n return blogUnwritten ? AI_SITE_BLOG_SLUGS.map((slug) => `/${slug}`) : []\n}\n\n/**\n * The layout's nodes with its links to the retired starter home pointed at\n * the new home, and a link for each proposed entry the header does not\n * already carry, placed in the header's toolbar after its existing links.\n * `null` when nothing changes, or when the layout has no toolbar to hold an\n * entry and no link to repoint.\n */\nexport function aiLayoutWithNavigation(\n nodes: NodesMap,\n input: {\n entries: readonly NavEntry[]\n retiredHomeId?: string | null\n homeId?: string | null\n /** Paths whose links come out: a blog the start linked and never wrote (AGL-3660). */\n droppedHrefs?: readonly string[]\n },\n): NodesMap | null {\n const map: Record<string, MutableNode> = {}\n for (const [id, node] of Object.entries(nodes as unknown as Record<string, MutableNode>)) {\n map[id] = { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n }\n let changed = false\n // The header, the phone menu and the footer link a blog by its path before\n // its posts are written (AGL-3660); a start whose posts were not written\n // publishes none of those links to a page that does not exist.\n const dropped = new Set(input.droppedHrefs ?? [])\n if (dropped.size) {\n const gone = new Set(\n Object.entries(map)\n .filter(([, node]) => node.componentId === 'muiScreenLink' && dropped.has(String(node.props?.['href'] ?? '')))\n .map(([id]) => id),\n )\n if (gone.size) {\n for (const id of gone) delete map[id]\n for (const node of Object.values(map)) {\n if (node.nodes?.some((child) => gone.has(child))) node.nodes = node.nodes.filter((child) => !gone.has(child))\n }\n changed = true\n }\n }\n const links = Object.values(map).filter((node) => node.componentId === 'muiScreenLink')\n if (input.retiredHomeId && input.homeId) {\n for (const link of links) {\n if (link.props?.['screenId'] === input.retiredHomeId) {\n link.props = { ...link.props, screenId: input.homeId }\n changed = true\n }\n }\n }\n const linked = new Set(links.map((link) => link.props?.['screenId']).filter(Boolean))\n const toolbar = Object.values(map).find((node) => node.componentId === 'muiToolbar')\n const missing = input.entries.filter((entry) => !linked.has(entry.screenId))\n if (toolbar && missing.length) {\n const children = toolbar.nodes ?? []\n // After the last link already in the toolbar (the brand), before the\n // search, the mode switch and the call to action.\n const lastLink = children.reduce(\n (at, id, index) => (map[id]?.componentId === 'muiScreenLink' ? index : at),\n -1,\n )\n const added = missing.map((entry) => {\n const id = `ai_nav_${createResourceUid()}`\n map[id] = {\n $id: id,\n componentId: 'muiScreenLink',\n pluginId: 'mui',\n parentId: toolbar.$id,\n props: { children: entry.label, screenId: entry.screenId, renderAs: 'link', color: 'inherit' },\n sx: { textDecoration: 'none', whiteSpace: 'nowrap' },\n nodes: [],\n }\n return id\n })\n toolbar.nodes = [...children.slice(0, lastLink + 1), ...added, ...children.slice(lastLink + 1)]\n changed = true\n }\n return changed ? (map as unknown as NodesMap) : null\n}\n\n/**\n * Publishes the pages a guided site start built. Never throws for a page; a\n * Firestore failure of the publish write itself reports every page as a\n * draft, for the same reason, and is logged.\n */\nexport async function aiPublishGuidedSite(\n firestore: Firestore,\n input: {\n job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>\n outputs: readonly AiJobOutput[]\n now: Date\n /** The start owed its blog's posts and wrote none (AGL-3660): its links to the blog come out. */\n blogUnwritten?: boolean\n },\n deps: AiSitePublishDeps = {},\n): Promise<AiJobSitePublish> {\n const sendSitePublished = deps.sendSitePublished ?? sendGa4SitePublished\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const { job, now } = input\n const hostId = job.hostId ?? ''\n const pages = input.outputs.filter(\n (output, index, all) =>\n output.resource === 'screen' &&\n (output.hostId ?? hostId) === hostId &&\n all.findIndex((other) => other.resource === 'screen' && other.id === output.id) === index,\n )\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n const result: AiJobSitePublish = {\n liveUrl: aiSiteLiveUrl({ cname: host.get('cname'), subdomain: host.get('subdomain') }),\n published: [],\n drafts: [],\n }\n if (!host.exists || !pages.length) return result\n\n const before = { ...((host.get('screens') ?? {}) as Record<string, string>) }\n const placeholder = (host.get('defaultHomeScreenId') as string | undefined) || null\n const routing = { ...before }\n const entries: Record<string, string> = {}\n const accepted: Array<{ id: string; label: string; path: string; slug: string }> = []\n for (const page of pages) {\n const snapshot = await hostRef.collection('screens').doc(page.id).get()\n const label = String(snapshot.get('displayName') ?? page.label ?? '') || page.label\n if (!snapshot.exists || snapshot.get('deletedAt') != null) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_DELETED })\n continue\n }\n const slug = normalizeScreenSlug(String(snapshot.get('slug') ?? ''))\n if (!slug) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_NO_ADDRESS })\n continue\n }\n const owner = blockingRouteOwner(routing, slug, placeholder)\n if (owner && owner !== page.id) {\n result.drafts.push({ id: page.id, label, reason: aiSitePublishTakenCopy(slug) })\n continue\n }\n routing[page.id] = slug\n entries[page.id] = slug\n accepted.push({ id: page.id, label, path: slug, slug })\n }\n if (!accepted.length) return result\n\n // The first real home page replaces the placeholder in the same write.\n const home = accepted.find((page) => page.path === SCREEN_ROOT_PATH) ?? null\n const releases =\n home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH\n ? placeholder\n : null\n // An untouched starter home (still its one first version) is retired to the\n // trash; one its owner edited stays in Pages as their draft.\n const retire = releases\n ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1\n : false\n\n // The header of the layout the pages render inside learns their entries.\n const layoutWrite = await aiNavigationLayoutWrite(firestore, {\n hostId,\n pageIds: accepted.map((page) => page.id),\n entries: navEntriesOf(pages).filter((entry) => entries[entry.screenId]),\n droppedHrefs: aiSiteUnwrittenBlogHrefs(input.blogUnwritten),\n retiredHomeId: releases,\n homeId: home?.id ?? null,\n jobId: job.$id,\n now,\n }).catch((error: unknown) => {\n console.error('ai site publish: navigation not applied', { hostId, jobId: job.$id, error })\n return null\n })\n\n const paths = [\n ...new Set(\n [\n ...Object.values(entries),\n ...(releases ? [SCREEN_ROOT_PATH] : []),\n ].map((path) => screenRoutePathToUrl(path)),\n ),\n ]\n const batch = firestore.batch()\n const hostUpdates: Record<string, unknown> = { updatedAt: now }\n for (const [id, path] of Object.entries(entries)) hostUpdates[`screens.${id}`] = path\n if (releases) {\n hostUpdates[`screens.${releases}`] = FieldValue.delete()\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n batch.set(\n hostRef.collection('screens').doc(releases),\n { publishedAt: FieldValue.delete(), ...(retire ? { deletedAt: now } : {}) },\n { merge: true },\n )\n } else if (placeholder && entries[placeholder]) {\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n }\n batch.update(hostRef, hostUpdates)\n for (const page of accepted) {\n batch.set(hostRef.collection('screens').doc(page.id), { slug: page.slug, publishedAt: now }, { merge: true })\n }\n if (layoutWrite) {\n batch.create(layoutWrite.versionRef, layoutWrite.version)\n batch.set(layoutWrite.layoutRef, { versionId: layoutWrite.versionRef.id, updatedAt: now }, { merge: true })\n }\n // The announce, written down in the publish's own batch (AGL-2575): a\n // layout change reaches every page, so it asks for the whole site.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n batch.set(outboxRef, {\n hostId,\n paths: paths.length ? paths : [SCREEN_ROOT_PATH],\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n ...(layoutWrite ? { entireHost: true } : {}),\n })\n try {\n await batch.commit()\n } catch (error) {\n console.error('ai site publish failed', { hostId, jobId: job.$id, error })\n for (const page of accepted) result.drafts.push({ id: page.id, label: page.label, reason: AI_SITE_PUBLISH_FAILED })\n return result\n }\n result.published = accepted.map(({ id, label, path }) => ({ id, label, path: screenRoutePathToUrl(path) }))\n\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({\n hostIds: [hostId],\n reason: 'guided AI site start published',\n ...(layoutWrite ? {} : { paths: { [hostId]: paths } }),\n })\n if (dropped.complete) await outboxRef.delete().catch(() => undefined)\n // A site that came alive with nobody's browser on the publish (AGL-1589).\n await sendSitePublished({ hostId, firstPublish: isFirstPublishedRoute(before, placeholder) })\n return result\n}\n\ninterface LayoutWrite {\n layoutRef: FirebaseFirestore.DocumentReference\n versionRef: FirebaseFirestore.DocumentReference\n version: Record<string, unknown>\n}\n\n/**\n * The new live version of the layout the published pages render inside, with\n * their navigation entries and the home link moved off a retired starter;\n * `null` when there is nothing to change or no one layout to change.\n */\nasync function aiNavigationLayoutWrite(\n firestore: Firestore,\n input: {\n hostId: string\n pageIds: readonly string[]\n entries: readonly NavEntry[]\n droppedHrefs: readonly string[]\n retiredHomeId: string | null\n homeId: string | null\n jobId: string\n now: Date\n },\n): Promise<LayoutWrite | null> {\n if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length) return null\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const layoutIds = new Set<string>()\n for (const id of input.pageIds) {\n const screen = await hostRef.collection('screens').doc(id).get()\n const versionId = screen.get('versionId')\n const version =\n typeof versionId === 'string' && versionId\n ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get()\n : null\n const layoutId = version?.get('layoutId') ?? screen.get('layoutId')\n if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId)\n }\n if (layoutIds.size !== 1) return null\n const [layoutId] = [...layoutIds]\n const layoutRef = hostRef.collection('layouts').doc(layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const nodes = current.exists ? decodeStoredNodes<NodesMap>(current.get('nodes')) : null\n if (!nodes) return null\n const next = aiLayoutWithNavigation(nodes, input)\n const packed = next ? encodeStoredNodes(next) : null\n if (!next || !packed) return null\n const versionRef = layoutRef.collection('versions').doc(createResourceUid())\n return {\n layoutRef,\n versionRef,\n version: {\n layoutId,\n hostId: input.hostId,\n displayName: 'Navigation for the new pages',\n nodes: Buffer.from(packed),\n createdAt: input.now,\n updatedAt: input.now,\n aiJobId: input.jobId,\n },\n }\n}\n"],"names":["isFirstPublishedRoute","createResourceUid","hostPublicOrigin","blockingRouteOwner","normalizeScreenSlug","SCREEN_ROOT_PATH","screenRoutePathToUrl","decodeStoredNodes","encodeStoredNodes","dropPluginSiteCache","sendGa4SitePublished","FieldValue","AI_SITE_BLOG_SLUGS","aiJobPublishesBuild","job","kind","inputs","aiJobPublishesSite","aiSiteLiveUrl","host","text","value","trim","origin","cname","subdomain","AI_SITE_PUBLISH_DELETED","AI_SITE_PUBLISH_NO_ADDRESS","aiSitePublishTakenCopy","path","AI_SITE_PUBLISH_FAILED","navEntriesOf","outputs","entries","output","resource","navigation","proposal","label","some","entry","screenId","id","push","aiSiteUnwrittenBlogHrefs","blogUnwritten","map","slug","aiLayoutWithNavigation","nodes","input","node","Object","props","changed","dropped","Set","droppedHrefs","size","gone","filter","componentId","has","String","values","child","links","retiredHomeId","homeId","link","linked","Boolean","toolbar","find","missing","length","children","lastLink","reduce","at","index","added","$id","pluginId","parentId","renderAs","color","sx","textDecoration","whiteSpace","slice","aiPublishGuidedSite","firestore","deps","accepted","sendSitePublished","dropCache","now","hostId","pages","all","findIndex","other","hostRef","collection","doc","get","result","liveUrl","published","drafts","exists","before","placeholder","routing","page","snapshot","reason","owner","home","releases","retire","limit","layoutWrite","aiNavigationLayoutWrite","pageIds","jobId","catch","error","console","paths","batch","hostUpdates","updatedAt","delete","set","publishedAt","deletedAt","merge","update","create","versionRef","version","layoutRef","versionId","outboxRef","createdAt","serverTimestamp","attempts","entireHost","commit","hostIds","complete","undefined","firstPublish","layoutIds","screen","layoutId","add","layout","currentVersionId","current","next","packed","displayName","Buffer","from","aiJobId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-publish.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\nimport {\n blockingRouteOwner,\n normalizeScreenSlug,\n SCREEN_ROOT_PATH,\n screenRoutePathToUrl,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\nimport { AI_SITE_BLOG_SLUGS } from '../model/ai-site-job'\nimport { aiLayoutWithStoreLinks, type AiStoreLayoutLinks } from '../model/ai-layout-store-links'\nimport { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from './ai-job-store-links-layout'\n\n/**\n * A store's links a build drafted into the site's own layout (AGL-3676): the\n * draft version, the links in it, and the site's page addresses they were\n * checked against. Its publish makes that version the live one, or — where\n * the layout changed since the draft was made — adds the same links to the\n * version that is live now.\n */\nexport interface AiSitePublishStoreLinks {\n layoutId: string\n versionId: string\n links: AiStoreLayoutLinks\n screenPaths?: Readonly<Record<string, string>>\n}\n\n/**\n * A guided site start publishes what it built (AGL-3596).\n *\n * A person who asked for \"a website\" in the guided start and watched it build\n * was then handed drafts: a site whose address still answered with the\n * starter, or with nothing. A `site` job started from the guided start (its\n * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends\n * by putting every page it built on the site, through the same writes a\n * person's Publish makes:\n *\n * - each page's address in the host's routing map, `publishedAt` on the page,\n * in ONE batch with the publish outbox entry that drops the live site's\n * cached pages (AGL-2575), the drop itself attempted at once;\n * - the home page takes `/` from the starter home page the site was created\n * with (AGL-3408), and an untouched starter goes to the trash rather than\n * staying beside it as a second \"Home\";\n * - the navigation entries the pages proposed are added to the header of\n * the layout they render inside, as a new live version of that layout;\n * - `site_published` once, server-side, as the scheduled publish sends it.\n *\n * A page that cannot be published — deleted meanwhile, no address, an address\n * another page holds — stays a draft and is reported with the plain sentence\n * for why. It never fails the job: the site is still built, and the person\n * fixes that one page in Pages. Every other job kind keeps producing drafts.\n *\n * The allowance is respected by construction: a draft is counted against the\n * plan's pages when it is written (`aiDraftBandRefusal`), so publishing\n * drafts adds none, and retiring the starter frees the one it held.\n */\n\nexport interface AiSitePublishDeps {\n sendSitePublished?: typeof sendGa4SitePublished\n dropCache?: typeof dropPluginSiteCache\n}\n\n/**\n * Whether a build puts its pages live (AGL-3616): only where its request\n * asked to (`publish`, set by Assist's proposal) AND the member ticked the\n * plan card's box when confirming (`publishConfirmed`, set by the resume\n * door). Every other build leaves drafts.\n */\nexport function aiJobPublishesBuild(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'build' && job.inputs?.['publish'] === true && job.inputs?.['publishConfirmed'] === true\n}\n\n/** Whether a finished job of this shape publishes what it built. */\nexport function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && job.inputs?.['autoConfirm'] === true\n}\n\n/**\n * Where a site answers: its custom domain, else its subdomain under the\n * deployment's tenant apex — never a hard-coded platform host, so a\n * self-hosted deployment links to its own sites.\n */\nexport function aiSiteLiveUrl(host: { cname?: unknown; subdomain?: unknown }): string | null {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const origin = hostPublicOrigin({ cname: text(host.cname) || null, subdomain: text(host.subdomain) || null })\n return origin ? `${origin}/` : null\n}\n\n/** The sentences a page that stayed a draft is reported with. */\nexport const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.'\nexport const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.'\nexport const aiSitePublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`\nexport const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.'\n\n/** A navigation entry a page proposed, as the page step reports it. */\ninterface NavEntry {\n screenId: string\n label: string\n}\n\nfunction navEntriesOf(outputs: readonly AiJobOutput[]): NavEntry[] {\n const entries: NavEntry[] = []\n for (const output of outputs) {\n if (output.resource !== 'screen') continue\n const navigation = output.proposal?.['navigation'] as { label?: unknown } | undefined\n const label = typeof navigation?.label === 'string' ? navigation.label.trim() : ''\n if (label && !entries.some((entry) => entry.screenId === output.id)) {\n entries.push({ screenId: output.id, label })\n }\n }\n return entries\n}\n\ntype MutableNode = {\n $id: string\n componentId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\n/**\n * The blog paths a start's layout links and must not publish (AGL-3660): its\n * header, phone menu and footer link the blog by path while the posts part is\n * still owed, so a start whose posts part then failed or was skipped drops\n * every address the blog could have taken. None while the blog was written.\n */\nexport function aiSiteUnwrittenBlogHrefs(blogUnwritten: boolean | undefined): string[] {\n return blogUnwritten ? AI_SITE_BLOG_SLUGS.map((slug) => `/${slug}`) : []\n}\n\n/**\n * The layout's nodes with its links to the retired starter home pointed at\n * the new home, and a link for each proposed entry the header does not\n * already carry, placed in the header's toolbar after its existing links.\n * `null` when nothing changes, or when the layout has no toolbar to hold an\n * entry and no link to repoint.\n */\nexport function aiLayoutWithNavigation(\n nodes: NodesMap,\n input: {\n entries: readonly NavEntry[]\n retiredHomeId?: string | null\n homeId?: string | null\n /** Paths whose links come out: a blog the start linked and never wrote (AGL-3660). */\n droppedHrefs?: readonly string[]\n },\n): NodesMap | null {\n const map: Record<string, MutableNode> = {}\n for (const [id, node] of Object.entries(nodes as unknown as Record<string, MutableNode>)) {\n map[id] = { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n }\n let changed = false\n // The header, the phone menu and the footer link a blog by its path before\n // its posts are written (AGL-3660); a start whose posts were not written\n // publishes none of those links to a page that does not exist.\n const dropped = new Set(input.droppedHrefs ?? [])\n if (dropped.size) {\n const gone = new Set(\n Object.entries(map)\n .filter(([, node]) => node.componentId === 'muiScreenLink' && dropped.has(String(node.props?.['href'] ?? '')))\n .map(([id]) => id),\n )\n if (gone.size) {\n for (const id of gone) delete map[id]\n for (const node of Object.values(map)) {\n if (node.nodes?.some((child) => gone.has(child))) node.nodes = node.nodes.filter((child) => !gone.has(child))\n }\n changed = true\n }\n }\n const links = Object.values(map).filter((node) => node.componentId === 'muiScreenLink')\n if (input.retiredHomeId && input.homeId) {\n for (const link of links) {\n if (link.props?.['screenId'] === input.retiredHomeId) {\n link.props = { ...link.props, screenId: input.homeId }\n changed = true\n }\n }\n }\n const linked = new Set(links.map((link) => link.props?.['screenId']).filter(Boolean))\n const toolbar = Object.values(map).find((node) => node.componentId === 'muiToolbar')\n const missing = input.entries.filter((entry) => !linked.has(entry.screenId))\n if (toolbar && missing.length) {\n const children = toolbar.nodes ?? []\n // After the last link already in the toolbar (the brand), before the\n // search, the mode switch and the call to action.\n const lastLink = children.reduce(\n (at, id, index) => (map[id]?.componentId === 'muiScreenLink' ? index : at),\n -1,\n )\n const added = missing.map((entry) => {\n const id = `ai_nav_${createResourceUid()}`\n map[id] = {\n $id: id,\n componentId: 'muiScreenLink',\n pluginId: 'mui',\n parentId: toolbar.$id,\n props: { children: entry.label, screenId: entry.screenId, renderAs: 'link', color: 'inherit' },\n sx: { textDecoration: 'none', whiteSpace: 'nowrap' },\n nodes: [],\n }\n return id\n })\n toolbar.nodes = [...children.slice(0, lastLink + 1), ...added, ...children.slice(lastLink + 1)]\n changed = true\n }\n return changed ? (map as unknown as NodesMap) : null\n}\n\n/**\n * Publishes the pages a guided site start built. Never throws for a page; a\n * Firestore failure of the publish write itself reports every page as a\n * draft, for the same reason, and is logged.\n */\nexport async function aiPublishGuidedSite(\n firestore: Firestore,\n input: {\n job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>\n outputs: readonly AiJobOutput[]\n now: Date\n /** The start owed its blog's posts and wrote none (AGL-3660): its links to the blog come out. */\n blogUnwritten?: boolean\n /** Paths the layout links that the start did not write, such as a store page (AGL-3676): their links come out. */\n unwrittenHrefs?: readonly string[]\n /** A store's links a build drafted into the site's own layout (AGL-3676), which go live with its pages. */\n storeLinks?: AiSitePublishStoreLinks | null\n },\n deps: AiSitePublishDeps = {},\n): Promise<AiJobSitePublish> {\n const sendSitePublished = deps.sendSitePublished ?? sendGa4SitePublished\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const { job, now } = input\n const hostId = job.hostId ?? ''\n const pages = input.outputs.filter(\n (output, index, all) =>\n output.resource === 'screen' &&\n (output.hostId ?? hostId) === hostId &&\n all.findIndex((other) => other.resource === 'screen' && other.id === output.id) === index,\n )\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n const result: AiJobSitePublish = {\n liveUrl: aiSiteLiveUrl({ cname: host.get('cname'), subdomain: host.get('subdomain') }),\n published: [],\n drafts: [],\n }\n if (!host.exists || !pages.length) return result\n\n const before = { ...((host.get('screens') ?? {}) as Record<string, string>) }\n const placeholder = (host.get('defaultHomeScreenId') as string | undefined) || null\n const routing = { ...before }\n const entries: Record<string, string> = {}\n const accepted: Array<{ id: string; label: string; path: string; slug: string }> = []\n for (const page of pages) {\n const snapshot = await hostRef.collection('screens').doc(page.id).get()\n const label = String(snapshot.get('displayName') ?? page.label ?? '') || page.label\n if (!snapshot.exists || snapshot.get('deletedAt') != null) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_DELETED })\n continue\n }\n const slug = normalizeScreenSlug(String(snapshot.get('slug') ?? ''))\n if (!slug) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_NO_ADDRESS })\n continue\n }\n const owner = blockingRouteOwner(routing, slug, placeholder)\n if (owner && owner !== page.id) {\n result.drafts.push({ id: page.id, label, reason: aiSitePublishTakenCopy(slug) })\n continue\n }\n routing[page.id] = slug\n entries[page.id] = slug\n accepted.push({ id: page.id, label, path: slug, slug })\n }\n if (!accepted.length) return result\n\n // The first real home page replaces the placeholder in the same write.\n const home = accepted.find((page) => page.path === SCREEN_ROOT_PATH) ?? null\n const releases =\n home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH\n ? placeholder\n : null\n // An untouched starter home (still its one first version) is retired to the\n // trash; one its owner edited stays in Pages as their draft.\n const retire = releases\n ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1\n : false\n\n // The header of the layout the pages render inside learns their entries.\n const layoutWrite = await aiNavigationLayoutWrite(firestore, {\n hostId,\n pageIds: accepted.map((page) => page.id),\n entries: navEntriesOf(pages).filter((entry) => entries[entry.screenId]),\n droppedHrefs: [...aiSiteUnwrittenBlogHrefs(input.blogUnwritten), ...(input.unwrittenHrefs ?? [])],\n retiredHomeId: releases,\n homeId: home?.id ?? null,\n jobId: job.$id,\n now,\n storeLinks: input.storeLinks ?? null,\n }).catch((error: unknown) => {\n console.error('ai site publish: navigation not applied', { hostId, jobId: job.$id, error })\n return null\n })\n // A store's links drafted into a layout the pages' navigation did not touch go live on their own.\n const storeLinks = input.storeLinks ?? null\n const storeLinksWrite =\n storeLinks && layoutWrite?.layoutRef.id !== storeLinks.layoutId\n ? await aiLayoutVersionWrite(firestore, {\n hostId,\n layoutId: storeLinks.layoutId,\n transform: () => null,\n storeLinks,\n jobId: job.$id,\n now,\n }).catch((error: unknown) => {\n console.error('ai site publish: store links not applied', { hostId, jobId: job.$id, error })\n return null\n })\n : null\n const layoutWrites = [layoutWrite, storeLinksWrite].filter((write): write is LayoutWrite => Boolean(write))\n\n const paths = [\n ...new Set(\n [\n ...Object.values(entries),\n ...(releases ? [SCREEN_ROOT_PATH] : []),\n ].map((path) => screenRoutePathToUrl(path)),\n ),\n ]\n const batch = firestore.batch()\n const hostUpdates: Record<string, unknown> = { updatedAt: now }\n for (const [id, path] of Object.entries(entries)) hostUpdates[`screens.${id}`] = path\n if (releases) {\n hostUpdates[`screens.${releases}`] = FieldValue.delete()\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n batch.set(\n hostRef.collection('screens').doc(releases),\n { publishedAt: FieldValue.delete(), ...(retire ? { deletedAt: now } : {}) },\n { merge: true },\n )\n } else if (placeholder && entries[placeholder]) {\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n }\n batch.update(hostRef, hostUpdates)\n for (const page of accepted) {\n batch.set(hostRef.collection('screens').doc(page.id), { slug: page.slug, publishedAt: now }, { merge: true })\n }\n for (const write of layoutWrites) {\n if (write.version) batch.create(write.versionRef, write.version)\n batch.set(write.layoutRef, { versionId: write.versionRef.id, updatedAt: now }, { merge: true })\n }\n // The announce, written down in the publish's own batch (AGL-2575): a\n // layout change reaches every page, so it asks for the whole site.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n batch.set(outboxRef, {\n hostId,\n paths: paths.length ? paths : [SCREEN_ROOT_PATH],\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n ...(layoutWrites.length ? { entireHost: true } : {}),\n })\n try {\n await batch.commit()\n } catch (error) {\n console.error('ai site publish failed', { hostId, jobId: job.$id, error })\n for (const page of accepted) result.drafts.push({ id: page.id, label: page.label, reason: AI_SITE_PUBLISH_FAILED })\n return result\n }\n result.published = accepted.map(({ id, label, path }) => ({ id, label, path: screenRoutePathToUrl(path) }))\n\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({\n hostIds: [hostId],\n reason: 'guided AI site start published',\n ...(layoutWrites.length ? {} : { paths: { [hostId]: paths } }),\n })\n if (dropped.complete) await outboxRef.delete().catch(() => undefined)\n // A site that came alive with nobody's browser on the publish (AGL-1589).\n await sendSitePublished({ hostId, firstPublish: isFirstPublishedRoute(before, placeholder) })\n return result\n}\n\ninterface LayoutWrite {\n layoutRef: FirebaseFirestore.DocumentReference\n versionRef: FirebaseFirestore.DocumentReference\n /** The new version to create; `null` where the live version becomes one already written (a store's links draft). */\n version: Record<string, unknown> | null\n}\n\n/**\n * The new live version of the layout the published pages render inside, with\n * their navigation entries and the home link moved off a retired starter;\n * `null` when there is nothing to change or no one layout to change.\n */\nasync function aiNavigationLayoutWrite(\n firestore: Firestore,\n input: {\n hostId: string\n pageIds: readonly string[]\n entries: readonly NavEntry[]\n droppedHrefs: readonly string[]\n retiredHomeId: string | null\n homeId: string | null\n jobId: string\n now: Date\n storeLinks?: AiSitePublishStoreLinks | null\n },\n): Promise<LayoutWrite | null> {\n if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.storeLinks) return null\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const layoutIds = new Set<string>()\n for (const id of input.pageIds) {\n const screen = await hostRef.collection('screens').doc(id).get()\n const versionId = screen.get('versionId')\n const version =\n typeof versionId === 'string' && versionId\n ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get()\n : null\n const layoutId = version?.get('layoutId') ?? screen.get('layoutId')\n if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId)\n }\n if (layoutIds.size !== 1) return null\n const [layoutId] = [...layoutIds]\n return aiLayoutVersionWrite(firestore, {\n hostId: input.hostId,\n layoutId,\n transform: (nodes) => aiLayoutWithNavigation(nodes, input),\n storeLinks: input.storeLinks ?? null,\n jobId: input.jobId,\n now: input.now,\n })\n}\n\n/**\n * The new live version of one layout: `transform` applied to the version\n * live now. A store's links drafted into this layout (AGL-3676) start it from\n * that draft while the draft was made from the live version, and are added\n * to the live version otherwise; a draft with nothing more to add becomes\n * the live version as it is. `null` when nothing changes.\n */\nasync function aiLayoutVersionWrite(\n firestore: Firestore,\n input: {\n hostId: string\n layoutId: string\n transform: (nodes: NodesMap) => NodesMap | null\n storeLinks: AiSitePublishStoreLinks | null\n jobId: string\n now: Date\n },\n): Promise<LayoutWrite | null> {\n const { layoutId } = input\n const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const links = input.storeLinks?.layoutId === layoutId ? input.storeLinks : null\n const draftRef = links ? layoutRef.collection('versions').doc(links.versionId) : null\n const draft = draftRef ? await draftRef.get() : null\n const fromDraft = Boolean(draft?.exists && draft.get(STORE_LINKS_SOURCE_FIELD) === currentVersionId)\n const base = fromDraft && draft ? draft : current\n const nodes = base.exists ? decodeStoredNodes<NodesMap>(base.get('nodes')) : null\n if (!nodes) return null\n let next = input.transform(nodes)\n if (links && !fromDraft) {\n const added = aiLayoutWithStoreLinks((next ?? nodes) as never, links.links, links.screenPaths)\n next = (added.nodes as NodesMap | null) ?? next\n }\n if (!next) return fromDraft && draftRef ? { layoutRef, versionRef: draftRef, version: null } : null\n const packed = encodeStoredNodes(next)\n if (!packed) return null\n const versionRef = layoutRef.collection('versions').doc(createResourceUid())\n return {\n layoutRef,\n versionRef,\n version: {\n layoutId,\n hostId: input.hostId,\n displayName: 'Navigation for the new pages',\n nodes: Buffer.from(packed),\n createdAt: input.now,\n updatedAt: input.now,\n aiJobId: input.jobId,\n },\n }\n}\n"],"names":["isFirstPublishedRoute","createResourceUid","hostPublicOrigin","blockingRouteOwner","normalizeScreenSlug","SCREEN_ROOT_PATH","screenRoutePathToUrl","decodeStoredNodes","encodeStoredNodes","dropPluginSiteCache","sendGa4SitePublished","FieldValue","AI_SITE_BLOG_SLUGS","aiLayoutWithStoreLinks","AI_STORE_LINKS_SOURCE_FIELD","STORE_LINKS_SOURCE_FIELD","aiJobPublishesBuild","job","kind","inputs","aiJobPublishesSite","aiSiteLiveUrl","host","text","value","trim","origin","cname","subdomain","AI_SITE_PUBLISH_DELETED","AI_SITE_PUBLISH_NO_ADDRESS","aiSitePublishTakenCopy","path","AI_SITE_PUBLISH_FAILED","navEntriesOf","outputs","entries","output","resource","navigation","proposal","label","some","entry","screenId","id","push","aiSiteUnwrittenBlogHrefs","blogUnwritten","map","slug","aiLayoutWithNavigation","nodes","input","node","Object","props","changed","dropped","Set","droppedHrefs","size","gone","filter","componentId","has","String","values","child","links","retiredHomeId","homeId","link","linked","Boolean","toolbar","find","missing","length","children","lastLink","reduce","at","index","added","$id","pluginId","parentId","renderAs","color","sx","textDecoration","whiteSpace","slice","aiPublishGuidedSite","firestore","deps","accepted","sendSitePublished","dropCache","now","hostId","pages","all","findIndex","other","hostRef","collection","doc","get","result","liveUrl","published","drafts","exists","before","placeholder","routing","page","snapshot","reason","owner","home","releases","retire","limit","layoutWrite","aiNavigationLayoutWrite","pageIds","unwrittenHrefs","jobId","storeLinks","catch","error","console","storeLinksWrite","layoutRef","layoutId","aiLayoutVersionWrite","transform","layoutWrites","write","paths","batch","hostUpdates","updatedAt","delete","set","publishedAt","deletedAt","merge","update","version","create","versionRef","versionId","outboxRef","createdAt","serverTimestamp","attempts","entireHost","commit","hostIds","complete","undefined","firstPublish","layoutIds","screen","add","layout","currentVersionId","current","draftRef","draft","fromDraft","base","next","screenPaths","packed","displayName","Buffer","from","aiJobId"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { StockPhoto } from '@aglyn/aglyn/plugin-manager/stock-photo-provider';
18
+ /** Bronze mannequin hands: "wax" is the only word of the shop's world. */
19
+ export declare const MANNEQUIN_HANDS: StockPhoto;
20
+ /** A candlemaker pouring wax: the maker at work. */
21
+ export declare const CANDLE_MAKING: StockPhoto;
22
+ /** Resin fluid art: "melt" and "wax", never a wax melt. */
23
+ export declare const FLUID_ART: StockPhoto;
24
+ /** One taper candle in a brass holder. */
25
+ export declare const TAPER_CANDLE: StockPhoto;
26
+ /** Wax melts beside a warmer. */
27
+ export declare const WAX_MELTS: StockPhoto;
28
+ /** Devotional candles decorated with crosses and names. */
29
+ export declare const DEVOTIONAL_CANDLES: StockPhoto;
30
+ /** A wrapped candle gift. */
31
+ export declare const CANDLE_GIFT: StockPhoto;
32
+ /** A bed with a book, a mug and a camera; a candle among its last tags. */
33
+ export declare const BED_SCENE: StockPhoto;
34
+ /** A plain soy candle in a jar. */
35
+ export declare const SOY_CANDLE: StockPhoto;
36
+ /** A perforated ceramic sculpture of a nude female torso. */
37
+ export declare const NUDE_TORSO: StockPhoto;
38
+ /** A potter at the wheel. */
39
+ export declare const POTTER_AT_WHEEL: StockPhoto;
40
+ /** Red and yellow blown glass art, fired in a kiln. */
41
+ export declare const GLASS_ART: StockPhoto;
42
+ /** Stoneware bowls on a shelf. */
43
+ export declare const STONEWARE_BOWLS: StockPhoto;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * THE EMBER & OAK START'S PHOTOS, IN PIXABAY'S SHAPE (AGL-3660): the beta.239
18
+ * production store "Ember & Oak Candle Co" ("A small-batch candle shop selling
19
+ * hand-poured soy candles, wax melts and gift sets online") placed the photos
20
+ * below, each described from what it showed. They are written in the shape
21
+ * Pixabay answers with (a page slug from the first tags, then the tags in its
22
+ * order), never searched for; the ids are this file's own, not Pixabay's.
23
+ *
24
+ * The bad ones, each beside a photo that should win:
25
+ * - The story band "Poured by hand, in small batches" ← bronze mannequin hands.
26
+ * - The "Wax melts" collection tile ← colourful resin fluid art.
27
+ * - The "Gifts for melt fans" tile ← devotional candles with crosses and names.
28
+ * - "Hand-Poured Soy Candle" ← a bed with a book, a mug and a camera.
29
+ * - "Wax Melt Gift Set" ← a single taper candle in a holder.
30
+ */ const hit = (id, slug, tags)=>({
31
+ provider: 'pixabay',
32
+ id: String(id),
33
+ width: 1280,
34
+ height: 1600,
35
+ pageUrl: `https://pixabay.com/photos/${slug}-${id}/`,
36
+ photographer: 'A Pixabay contributor',
37
+ tags: tags.split(', '),
38
+ downloadUrl: `https://pixabay.com/get/g${id}_1280.jpg`
39
+ });
40
+ /** Bronze mannequin hands: "wax" is the only word of the shop's world. */ export const MANNEQUIN_HANDS = hit(9100001, 'hands-mannequin-bronze', 'hands, mannequin, bronze, sculpture, wax, fingers, art, figure');
41
+ /** A candlemaker pouring wax: the maker at work. */ export const CANDLE_MAKING = hit(9100002, 'candle-making-pouring-wax', 'candle making, pouring wax, candle, wax, soy wax, workshop, handmade');
42
+ /** Resin fluid art: "melt" and "wax", never a wax melt. */ export const FLUID_ART = hit(9100003, 'fluid-art-abstract-resin', 'fluid art, abstract, resin, colorful, paint, wax, melt, liquid, pattern');
43
+ /** One taper candle in a brass holder. */ export const TAPER_CANDLE = hit(9100004, 'candle-taper-candlestick', 'candle, taper, candlestick, flame, candle holder, brass, light');
44
+ /** Wax melts beside a warmer. */ export const WAX_MELTS = hit(9100005, 'wax-melts-melt-warmer-scented', 'wax melts, melt warmer, scented, aroma, wax, fragrance, home');
45
+ /** Devotional candles decorated with crosses and names. */ export const DEVOTIONAL_CANDLES = hit(9100006, 'candles-gift-cross', 'candles, gift, cross, religious, church, decorated, name, baptism');
46
+ /** A wrapped candle gift. */ export const CANDLE_GIFT = hit(9100007, 'candle-gift-wrapped-scented', 'candle, gift, wrapped, scented, ribbon, present, soy candle');
47
+ /** A bed with a book, a mug and a camera; a candle among its last tags. */ export const BED_SCENE = hit(9100008, 'bed-book-mug-camera', 'bed, book, mug, camera, bedroom, cozy, morning, blanket, candle');
48
+ /** A plain soy candle in a jar. */ export const SOY_CANDLE = hit(9100009, 'candle-soy-candle-jar', 'candle, soy candle, jar, flame, candlelight, amber');
49
+ /*
50
+ * THE KILN & CLOVER START'S PHOTOS, IN PIXABAY'S SHAPE (AGL-3660): a fresh
51
+ * production portfolio, "Kiln & Clover Ceramics" ("A portfolio for a ceramic
52
+ * artist making wheel-thrown stoneware bowls, mugs and vases, with
53
+ * commissions and classes"), placed a nude torso sculpture in its "About the
54
+ * artist and the process" band and red and yellow glass art in its Work
55
+ * page's hero. Described from what they showed; the ids are this file's own.
56
+ */ /** A perforated ceramic sculpture of a nude female torso. */ export const NUDE_TORSO = hit(9200001, 'sculpture-ceramic-torso', 'sculpture, ceramic, torso, nude, woman, body, art, clay');
57
+ /** A potter at the wheel. */ export const POTTER_AT_WHEEL = hit(9200002, 'potter-pottery-wheel-clay', 'potter, pottery wheel, clay, ceramic, hands, workshop, stoneware');
58
+ /** Red and yellow blown glass art, fired in a kiln. */ export const GLASS_ART = hit(9200003, 'glass-art-blown-glass', 'glass art, blown glass, red, yellow, vase, kiln, handmade, colorful');
59
+ /** Stoneware bowls on a shelf. */ export const STONEWARE_BOWLS = hit(9200004, 'pottery-stoneware-bowls', 'pottery, stoneware, bowls, ceramics, handmade, glaze');
60
+
61
+ //# sourceMappingURL=ai-stock-photo-ember-oak-hits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.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 { StockPhoto } from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\n\n/**\n * THE EMBER & OAK START'S PHOTOS, IN PIXABAY'S SHAPE (AGL-3660): the beta.239\n * production store \"Ember & Oak Candle Co\" (\"A small-batch candle shop selling\n * hand-poured soy candles, wax melts and gift sets online\") placed the photos\n * below, each described from what it showed. They are written in the shape\n * Pixabay answers with (a page slug from the first tags, then the tags in its\n * order), never searched for; the ids are this file's own, not Pixabay's.\n *\n * The bad ones, each beside a photo that should win:\n * - The story band \"Poured by hand, in small batches\" ← bronze mannequin hands.\n * - The \"Wax melts\" collection tile ← colourful resin fluid art.\n * - The \"Gifts for melt fans\" tile ← devotional candles with crosses and names.\n * - \"Hand-Poured Soy Candle\" ← a bed with a book, a mug and a camera.\n * - \"Wax Melt Gift Set\" ← a single taper candle in a holder.\n */\n\nconst hit = (id: number, slug: string, tags: string): StockPhoto =>\n ({\n provider: 'pixabay',\n id: String(id),\n width: 1280,\n height: 1600,\n pageUrl: `https://pixabay.com/photos/${slug}-${id}/`,\n photographer: 'A Pixabay contributor',\n tags: tags.split(', '),\n downloadUrl: `https://pixabay.com/get/g${id}_1280.jpg`,\n }) as StockPhoto\n\n/** Bronze mannequin hands: \"wax\" is the only word of the shop's world. */\nexport const MANNEQUIN_HANDS = hit(\n 9100001,\n 'hands-mannequin-bronze',\n 'hands, mannequin, bronze, sculpture, wax, fingers, art, figure',\n)\n\n/** A candlemaker pouring wax: the maker at work. */\nexport const CANDLE_MAKING = hit(\n 9100002,\n 'candle-making-pouring-wax',\n 'candle making, pouring wax, candle, wax, soy wax, workshop, handmade',\n)\n\n/** Resin fluid art: \"melt\" and \"wax\", never a wax melt. */\nexport const FLUID_ART = hit(\n 9100003,\n 'fluid-art-abstract-resin',\n 'fluid art, abstract, resin, colorful, paint, wax, melt, liquid, pattern',\n)\n\n/** One taper candle in a brass holder. */\nexport const TAPER_CANDLE = hit(\n 9100004,\n 'candle-taper-candlestick',\n 'candle, taper, candlestick, flame, candle holder, brass, light',\n)\n\n/** Wax melts beside a warmer. */\nexport const WAX_MELTS = hit(\n 9100005,\n 'wax-melts-melt-warmer-scented',\n 'wax melts, melt warmer, scented, aroma, wax, fragrance, home',\n)\n\n/** Devotional candles decorated with crosses and names. */\nexport const DEVOTIONAL_CANDLES = hit(\n 9100006,\n 'candles-gift-cross',\n 'candles, gift, cross, religious, church, decorated, name, baptism',\n)\n\n/** A wrapped candle gift. */\nexport const CANDLE_GIFT = hit(\n 9100007,\n 'candle-gift-wrapped-scented',\n 'candle, gift, wrapped, scented, ribbon, present, soy candle',\n)\n\n/** A bed with a book, a mug and a camera; a candle among its last tags. */\nexport const BED_SCENE = hit(\n 9100008,\n 'bed-book-mug-camera',\n 'bed, book, mug, camera, bedroom, cozy, morning, blanket, candle',\n)\n\n/** A plain soy candle in a jar. */\nexport const SOY_CANDLE = hit(\n 9100009,\n 'candle-soy-candle-jar',\n 'candle, soy candle, jar, flame, candlelight, amber',\n)\n\n/*\n * THE KILN & CLOVER START'S PHOTOS, IN PIXABAY'S SHAPE (AGL-3660): a fresh\n * production portfolio, \"Kiln & Clover Ceramics\" (\"A portfolio for a ceramic\n * artist making wheel-thrown stoneware bowls, mugs and vases, with\n * commissions and classes\"), placed a nude torso sculpture in its \"About the\n * artist and the process\" band and red and yellow glass art in its Work\n * page's hero. Described from what they showed; the ids are this file's own.\n */\n\n/** A perforated ceramic sculpture of a nude female torso. */\nexport const NUDE_TORSO = hit(\n 9200001,\n 'sculpture-ceramic-torso',\n 'sculpture, ceramic, torso, nude, woman, body, art, clay',\n)\n\n/** A potter at the wheel. */\nexport const POTTER_AT_WHEEL = hit(\n 9200002,\n 'potter-pottery-wheel-clay',\n 'potter, pottery wheel, clay, ceramic, hands, workshop, stoneware',\n)\n\n/** Red and yellow blown glass art, fired in a kiln. */\nexport const GLASS_ART = hit(\n 9200003,\n 'glass-art-blown-glass',\n 'glass art, blown glass, red, yellow, vase, kiln, handmade, colorful',\n)\n\n/** Stoneware bowls on a shelf. */\nexport const STONEWARE_BOWLS = hit(\n 9200004,\n 'pottery-stoneware-bowls',\n 'pottery, stoneware, bowls, ceramics, handmade, glaze',\n)\n"],"names":["hit","id","slug","tags","provider","String","width","height","pageUrl","photographer","split","downloadUrl","MANNEQUIN_HANDS","CANDLE_MAKING","FLUID_ART","TAPER_CANDLE","WAX_MELTS","DEVOTIONAL_CANDLES","CANDLE_GIFT","BED_SCENE","SOY_CANDLE","NUDE_TORSO","POTTER_AT_WHEEL","GLASS_ART","STONEWARE_BOWLS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;CAcC,GAED,MAAMA,MAAM,CAACC,IAAYC,MAAcC,OACpC,CAAA;QACCC,UAAU;QACVH,IAAII,OAAOJ;QACXK,OAAO;QACPC,QAAQ;QACRC,SAAS,CAAC,2BAA2B,EAAEN,KAAK,CAAC,EAAED,GAAG,CAAC,CAAC;QACpDQ,cAAc;QACdN,MAAMA,KAAKO,KAAK,CAAC;QACjBC,aAAa,CAAC,yBAAyB,EAAEV,GAAG,SAAS,CAAC;IACxD,CAAA;AAEF,wEAAwE,GACxE,OAAO,MAAMW,kBAAkBZ,IAC7B,SACA,0BACA,kEACD;AAED,kDAAkD,GAClD,OAAO,MAAMa,gBAAgBb,IAC3B,SACA,6BACA,wEACD;AAED,yDAAyD,GACzD,OAAO,MAAMc,YAAYd,IACvB,SACA,4BACA,2EACD;AAED,wCAAwC,GACxC,OAAO,MAAMe,eAAef,IAC1B,SACA,4BACA,kEACD;AAED,+BAA+B,GAC/B,OAAO,MAAMgB,YAAYhB,IACvB,SACA,iCACA,gEACD;AAED,yDAAyD,GACzD,OAAO,MAAMiB,qBAAqBjB,IAChC,SACA,sBACA,qEACD;AAED,2BAA2B,GAC3B,OAAO,MAAMkB,cAAclB,IACzB,SACA,+BACA,+DACD;AAED,yEAAyE,GACzE,OAAO,MAAMmB,YAAYnB,IACvB,SACA,uBACA,mEACD;AAED,iCAAiC,GACjC,OAAO,MAAMoB,aAAapB,IACxB,SACA,yBACA,sDACD;AAED;;;;;;;CAOC,GAED,2DAA2D,GAC3D,OAAO,MAAMqB,aAAarB,IACxB,SACA,2BACA,2DACD;AAED,2BAA2B,GAC3B,OAAO,MAAMsB,kBAAkBtB,IAC7B,SACA,6BACA,oEACD;AAED,qDAAqD,GACrD,OAAO,MAAMuB,YAAYvB,IACvB,SACA,yBACA,uEACD;AAED,gCAAgC,GAChC,OAAO,MAAMwB,kBAAkBxB,IAC7B,SACA,2BACA,wDACD"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { StockPhoto } from '@aglyn/aglyn/plugin-manager/stock-photo-provider';
18
+ /** `cold-process soap bar` (vertical, min_height 900), answered 2026-10-10T19:26:33.890Z. */
19
+ export declare const COLD_PROCESS_SOAP_BAR: StockPhoto[];
20
+ /** `soap bar` (vertical, min_height 900), answered 2026-10-10T19:26:34.165Z. */
21
+ export declare const SOAP_BAR: StockPhoto[];
22
+ /** `soap bar trio` (vertical, min_height 900), answered 2026-10-10T19:26:34.432Z. */
23
+ export declare const SOAP_BAR_TRIO: StockPhoto[];
24
+ /** `body lotion` (vertical, min_height 900), answered 2026-10-10T19:26:35.053Z. */
25
+ export declare const BODY_LOTION: StockPhoto[];
26
+ /** `soap` (vertical, min_height 900), answered 2026-10-10T19:26:35.548Z. */
27
+ export declare const SOAP: StockPhoto[];
28
+ /** `bath gift box` (vertical, min_height 900), answered 2026-10-10T19:26:35.813Z. */
29
+ export declare const BATH_GIFT_BOX: StockPhoto[];
30
+ /** `body care` (vertical, min_height 900), answered 2026-10-10T19:27:19.471Z. */
31
+ export declare const BODY_CARE: StockPhoto[];
32
+ /** `shop` (horizontal, min_width 1600), answered 2026-10-10T19:27:18.994Z. */
33
+ export declare const SHOP: StockPhoto[];
34
+ /** `handmade soap` (horizontal, min_width 900), answered 2026-10-10T19:27:19.891Z. */
35
+ export declare const HANDMADE_SOAP: StockPhoto[];
36
+ /** `soap bars` (horizontal, min_width 900), answered 2026-10-10T19:27:19.255Z. */
37
+ export declare const SOAP_BARS: StockPhoto[];
38
+ /** `soap making` (horizontal, min_width 900), answered 2026-10-10T19:27:20.170Z. */
39
+ export declare const SOAP_MAKING: StockPhoto[];