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

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 (116) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +3 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +7 -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.js +6 -4
  13. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  14. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  16. package/src/lib/jobs/ai-build-capabilities.js +162 -2
  17. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
  19. package/src/lib/jobs/ai-job-build-step.js +85 -16
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  22. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  24. package/src/lib/jobs/ai-job-layout-language.js +13 -0
  25. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  26. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  27. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  28. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  29. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  30. package/src/lib/jobs/ai-job-page-language.js +21 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-page-sections.js +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
  35. package/src/lib/jobs/ai-job-plan-step.js +16 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  38. package/src/lib/jobs/ai-job-products-step.js +2 -2
  39. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
  41. package/src/lib/jobs/ai-job-settings-step.js +695 -0
  42. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  43. package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
  44. package/src/lib/jobs/ai-job-site-content.js +28 -12
  45. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  46. package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
  47. package/src/lib/jobs/ai-job-site-datasets.js +10 -3
  48. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  49. package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
  50. package/src/lib/jobs/ai-job-site-step.js +70 -10
  51. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  53. package/src/lib/jobs/ai-site-publish.js +203 -56
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
  56. package/src/lib/layout-language/ai-layout-compiler.js +26 -25
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  59. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  60. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
  62. package/src/lib/layout-language/ai-layout-frame.js +83 -13
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +34 -0
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  68. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  69. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  70. package/src/lib/model/ai-build-job.d.ts +23 -0
  71. package/src/lib/model/ai-build-job.js +102 -13
  72. package/src/lib/model/ai-build-job.js.map +1 -1
  73. package/src/lib/model/ai-build-plan.d.ts +25 -0
  74. package/src/lib/model/ai-build-plan.js +45 -1
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  77. package/src/lib/model/ai-credit-estimate.js +51 -0
  78. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  79. package/src/lib/model/ai-job-activity.js +4 -1
  80. package/src/lib/model/ai-job-activity.js.map +1 -1
  81. package/src/lib/model/ai-jobs.types.d.ts +10 -2
  82. package/src/lib/model/ai-jobs.types.js +4 -1
  83. package/src/lib/model/ai-jobs.types.js.map +1 -1
  84. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  85. package/src/lib/model/ai-layout-menus.js +518 -0
  86. package/src/lib/model/ai-layout-menus.js.map +1 -0
  87. package/src/lib/model/ai-products.d.ts +31 -1
  88. package/src/lib/model/ai-products.js +34 -1
  89. package/src/lib/model/ai-products.js.map +1 -1
  90. package/src/lib/model/ai-site-inventory.d.ts +15 -0
  91. package/src/lib/model/ai-site-inventory.js.map +1 -1
  92. package/src/lib/model/ai-site-job.d.ts +10 -0
  93. package/src/lib/model/ai-site-job.js +36 -9
  94. package/src/lib/model/ai-site-job.js.map +1 -1
  95. package/src/lib/model/ai-site-store-pages.js +1 -1
  96. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  97. package/src/lib/runtime/ai-doctrine-validators.d.ts +53 -0
  98. package/src/lib/runtime/ai-doctrine-validators.js +166 -25
  99. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  100. package/src/lib/runtime/ai-doctrine.js +7 -2
  101. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  102. package/src/lib/runtime/ai-palette.generated.js +1 -1
  103. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  104. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  105. package/src/lib/runtime/ai-products-generation.js +12 -6
  106. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  107. package/src/lib/runtime/site-inventory.d.ts +12 -0
  108. package/src/lib/runtime/site-inventory.js +29 -1
  109. package/src/lib/runtime/site-inventory.js.map +1 -1
  110. package/src/lib/server.js +12 -0
  111. package/src/lib/server.js.map +1 -1
  112. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  113. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  114. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  115. package/src/lib/tools/ai-products-tool.js +15 -5
  116. package/src/lib/tools/ai-products-tool.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-settings-step.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 { supportedTimeZones } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { inheritedMediaAlt } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { mediaNodeSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { normalizeLocaleTag } from '@aglyn/aglyn/app-utils/seo-locale'\nimport { isSeoTitleVariable } from '@aglyn/aglyn/app-utils/seo-title-variables'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { syncHostProjectionForMembers } from '@aglyn/tenant-data-admin/server/host-memberships'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `settings` step (AGL-3748): a change to what a member can change in the\n * site's settings — its name, its search defaults, its favicon, logo and\n * social image from the media library, its language, its time zone, its\n * contact details and its consent banner's mode — as a first-class build\n * operation, so \"rename my site to Juniper Clay Studio\", \"make the site\n * Spanish\" or \"set the SEO description\" is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the values as the item's arguments;\n * this checks each against the rule the console's own control holds it to\n * and writes it, so it costs no credits.\n *\n * ── Only the console's own fields ────────────────────────────────────────\n *\n * Every field written is one a settings card or form writes, under the same\n * name and to the same limit: `displayName` (Admin → General, 30), `seo.title`\n * (60), `seo.description` (155), `seo.separator` (3), `seo.titlePattern`\n * (120, its own variables only), `seo.entity.name`, `seo.entity.description`\n * (300), `seo.entity.telephone`, `business.supportEmail` and\n * `business.address` (the Business details card), `locales`/`defaultLocale`\n * (the Languages card), `timeZone` (one of the zones the console offers),\n * `consent.mode` (the consent banner card), and `seo.favicon`, `logoUrl` and\n * `seo.image` (with its size and alt) written as the media pickers write\n * them, through `mediaNodeSrc`.\n *\n * NEVER: the address (subdomain, domains), plugins, members and roles,\n * billing, lockdown, deletion, search engine indexing, analytics and\n * advertising ids, verification tokens, or turning the consent banner off.\n * Those are not arguments, so the planner cannot ask for them.\n *\n * ── Live on confirm ──────────────────────────────────────────────────────\n *\n * A setting has no draft: the console saves it straight to the host\n * document, and the tenant renders it from there. So does this, once the\n * person confirms the plan (Zach, 2026-10-12: live on confirm). The write\n * drops the site's cached pages as the console's door does\n * (`host-document-writes.ts`), with a site-wide publish outbox entry in the\n * same transaction for the drain to fire if the drop does not land.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The Firestore rules' own for these keys (`canWriteHostContent` and\n * `hostWritesFrozen`): a member with a write role on the site (admin, editor\n * or author), while no lockdown freezes the site, its org or the platform.\n * Asked at the door, again before the write, and the role once more inside\n * the transaction that writes.\n *\n * ── Idempotent ───────────────────────────────────────────────────────────\n *\n * The transaction compares each value with what is stored and writes only\n * what differs, so a pass asked again writes nothing and announces nothing.\n */\n\nexport const AI_SETTINGS_KIND = 'settings' as const\n\n/**\n * ASSUMED: the least time the step needs — the site, the org, up to three\n * media lookups in two libraries, and the one transaction that writes. No\n * model, so it fits an inline door.\n */\nexport const AI_JOB_SETTINGS_STEP_MINIMUM_MS = 5_000\n\nexport const AI_SETTINGS_NO_SITE_COPY = 'Open the site whose settings should change.'\nexport const AI_SETTINGS_ROLE_COPY = 'Changing the site’s settings needs the admin, editor or author role on this site.'\nexport const AI_SETTINGS_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_SETTINGS_NOTHING_COPY = 'The request named no site setting this could change.'\n\n/** Where a person finds what the op changed, as the build's notes name it. */\nexport const AI_SETTINGS_WHERE = 'the site’s Settings'\n\n/** The output's fixed id: one site's settings, whatever changed. */\nexport const AI_SETTINGS_OUTPUT_ID = 'site'\n\n/** The console's own limits, each from the control that edits the field. */\nexport const AI_SETTINGS_LIMITS = {\n /** Admin → General, Display name. */\n name: 30,\n /** Setup → SEO, Title. */\n seoTitle: 60,\n /** Setup → SEO, Description. */\n seoDescription: 155,\n /** Setup → SEO, Separator. */\n separator: 3,\n /** Setup → SEO, Title pattern. */\n titlePattern: 120,\n /** Setup → SEO, the publisher's description. */\n businessDescription: 300,\n businessName: 100,\n telephone: 40,\n supportEmail: 254,\n address: 200,\n} as const\n\n/** The Languages card's own pattern for one language code. */\nexport const AI_SETTINGS_LOCALE_PATTERN = /^[a-z]{2}(-[A-Za-z]{2,4})?$/\n\n/** The consent banner card's two modes; turning the banner off is not offered. */\nexport const AI_SETTINGS_CONSENT_MODES = ['geo', 'strict'] as const\n\n/** The media a setting may name, and the field each is written to. */\nexport const AI_SETTINGS_MEDIA_ARGS = ['favicon', 'logo', 'socialImage'] as const\nexport type AiSettingsMediaArg = (typeof AI_SETTINGS_MEDIA_ARGS)[number]\n\nconst MEDIA_ARG_NOUNS: Readonly<Record<AiSettingsMediaArg, string>> = {\n favicon: 'favicon',\n logo: 'logo',\n socialImage: 'social image',\n}\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_SETTINGS_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The site’s name as the console lists it. Visitors see the search title, not this.',\n maxLength: AI_SETTINGS_LIMITS.name,\n },\n seoTitle: {\n type: 'string',\n description: 'The search title pages fall back to, usually the business’s name.',\n maxLength: AI_SETTINGS_LIMITS.seoTitle,\n },\n seoDescription: {\n type: 'string',\n description: 'The search description pages fall back to: one or two plain sentences.',\n maxLength: AI_SETTINGS_LIMITS.seoDescription,\n },\n separator: {\n type: 'string',\n description: 'The character between a page’s name and the site title, such as | or ·.',\n maxLength: AI_SETTINGS_LIMITS.separator,\n },\n titlePattern: {\n type: 'string',\n description: 'How an untitled page is titled, using only {{page.name}}, {{site.separator}} and {{site.name}}.',\n maxLength: AI_SETTINGS_LIMITS.titlePattern,\n },\n businessName: {\n type: 'string',\n description: 'The business’s name as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessName,\n },\n businessDescription: {\n type: 'string',\n description: 'What the business is, in one sentence (its tagline), as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessDescription,\n },\n telephone: {\n type: 'string',\n description: 'The business’s phone number, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.telephone,\n },\n supportEmail: {\n type: 'string',\n description: 'The contact email pages and emails show, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.supportEmail,\n },\n address: {\n type: 'string',\n description: 'The business’s address on one line, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.address,\n },\n language: {\n type: 'string',\n description: 'The language the site is in, as a code such as en, es, fr or pt-BR. It does not translate the pages.',\n maxLength: 10,\n },\n timeZone: {\n type: 'string',\n description: 'The site’s time zone as an IANA name, such as America/Chicago.',\n maxLength: 64,\n },\n consentMode: {\n type: 'string',\n description: 'The cookie banner’s mode: geo (asks only where the law requires) or strict (asks every visitor).',\n enum: AI_SETTINGS_CONSENT_MODES,\n },\n favicon: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the browser tab icon.',\n maxLength: 200,\n },\n logo: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the site’s logo.',\n maxLength: 200,\n },\n socialImage: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the card shown when a page is shared.',\n maxLength: 200,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks, each value read and held to the console's rule. */\nexport interface AiSettingsRequest {\n name?: string\n seoTitle?: string\n seoDescription?: string\n separator?: string\n titlePattern?: string\n businessName?: string\n businessDescription?: string\n telephone?: string\n supportEmail?: string\n address?: string\n /** A language code the Languages card would accept. */\n language?: string\n timeZone?: string\n consentMode?: (typeof AI_SETTINGS_CONSENT_MODES)[number]\n favicon?: string\n logo?: string\n socialImage?: string\n}\n\nexport interface AiSettingsRead {\n request: AiSettingsRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst TELEPHONE = /^[+()0-9][0-9 ()+.\\-/]{4,38}[0-9)]$/\nconst PATTERN_VARIABLE = /\\{\\{\\s*([^}]*?)\\s*\\}\\}/g\n\n/** Whether a title pattern names only the variables the renderer knows. */\nexport function aiSettingsTitlePatternOk(pattern: string): boolean {\n if (/[{}]/.test(pattern.replace(PATTERN_VARIABLE, ''))) return false\n return [...pattern.matchAll(PATTERN_VARIABLE)].every((match) => isSeoTitleVariable(match[1]))\n}\n\n/** A language code, as the Languages card stores it, or `null`. */\nexport function aiSettingsLocale(value: string): string | null {\n const tag = normalizeLocaleTag(value)\n return tag && AI_SETTINGS_LOCALE_PATTERN.test(tag) ? tag : null\n}\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiSettingsRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSettingsRead {\n const request: AiSettingsRequest = {}\n const problems: string[] = []\n const bounded = (key: keyof typeof AI_SETTINGS_LIMITS, noun: string) => {\n const value = text(inputs?.[key])\n if (!value) return\n if (value.length > AI_SETTINGS_LIMITS[key]) {\n problems.push(`The ${noun} is longer than ${AI_SETTINGS_LIMITS[key]} characters.`)\n return\n }\n request[key] = value\n }\n bounded('name', 'site name')\n bounded('seoTitle', 'search title')\n bounded('seoDescription', 'search description')\n bounded('separator', 'title separator')\n bounded('businessName', 'business name')\n bounded('businessDescription', 'business description')\n bounded('address', 'address')\n bounded('titlePattern', 'title pattern')\n if (request.titlePattern && !aiSettingsTitlePatternOk(request.titlePattern)) {\n problems.push('The title pattern may use only {{page.name}}, {{site.separator}} and {{site.name}}.')\n delete request.titlePattern\n }\n bounded('telephone', 'phone number')\n if (request.telephone && !TELEPHONE.test(request.telephone)) {\n problems.push(`“${request.telephone}” is not a phone number.`)\n delete request.telephone\n }\n bounded('supportEmail', 'contact email')\n if (request.supportEmail && !EMAIL.test(request.supportEmail)) {\n problems.push(`“${request.supportEmail}” is not an email address.`)\n delete request.supportEmail\n }\n const language = text(inputs?.['language'])\n if (language) {\n const locale = aiSettingsLocale(language)\n if (locale) request.language = locale\n else problems.push(`“${language}” is not a language code such as es or pt-BR.`)\n }\n const timeZone = text(inputs?.['timeZone'])\n if (timeZone) {\n if (supportedTimeZones().includes(timeZone)) request.timeZone = timeZone\n else problems.push(`“${timeZone}” is not a time zone the site settings offer.`)\n }\n const consentMode = text(inputs?.['consentMode'])\n if (consentMode) {\n if ((AI_SETTINGS_CONSENT_MODES as readonly string[]).includes(consentMode)) {\n request.consentMode = consentMode as AiSettingsRequest['consentMode']\n } else problems.push('The cookie banner’s mode is geo or strict.')\n }\n for (const key of AI_SETTINGS_MEDIA_ARGS) {\n const value = text(inputs?.[key])\n if (value) request[key] = value.slice(0, 200)\n }\n return { request, problems }\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiSettingsRequestEmpty(request: AiSettingsRequest): boolean {\n return Object.keys(request).length === 0\n}\n\n// ── Media ─────────────────────────────────────────────────────────────────\n\n/** A media library row, as a setting may name it. */\nexport interface AiSettingsMediaRow {\n id: string\n fileName: string\n contentType: string\n url: string | null\n cdnPath: string | null\n contentHash: string | null\n private: boolean\n deleted: boolean\n /** The org library's sites; `undefined` on the site's own library. */\n visibleTo?: readonly string[] | null\n width: number | null\n height: number | null\n alt: string\n}\n\n/** The image a setting names, as written to the host. */\nexport interface AiSettingsMedia {\n src: string\n width: number | null\n height: number | null\n alt: string\n fileName: string\n}\n\nfunction mediaRowOf(doc: FirebaseFirestore.QueryDocumentSnapshot | FirebaseFirestore.DocumentSnapshot, org: boolean): AiSettingsMediaRow {\n const data = (doc.data() ?? {}) as Record<string, unknown>\n const number = (value: unknown) => (typeof value === 'number' && value > 0 ? value : null)\n return {\n id: doc.id,\n fileName: String(data['fileName'] ?? ''),\n contentType: String(data['contentType'] ?? ''),\n url: typeof data['url'] === 'string' ? data['url'] : null,\n cdnPath: typeof data['cdnPath'] === 'string' ? data['cdnPath'] : null,\n contentHash: typeof data['contentHash'] === 'string' ? data['contentHash'] : null,\n private: data['private'] === true,\n deleted: data['deletedAt'] != null,\n ...(org ? { visibleTo: Array.isArray(data['visibleTo']) ? (data['visibleTo'] as string[]) : null } : {}),\n width: number(data['width']),\n height: number(data['height']),\n alt: typeof data['alt'] === 'string' ? data['alt'] : '',\n }\n}\n\n/**\n * The rows a media name could be, from the site's own library and then its\n * org's: the one whose id it is, else those whose file name it is.\n */\nexport async function readAiSettingsMediaRows(\n firestore: Firestore,\n input: { hostId: string; orgId: string; source: string },\n): Promise<AiSettingsMediaRow[]> {\n const libraries = [\n { ref: firestore.collection('hosts').doc(input.hostId).collection('media'), org: false },\n { ref: firestore.collection('orgs').doc(input.orgId).collection('media'), org: true },\n ]\n const rows: AiSettingsMediaRow[] = []\n for (const library of libraries) {\n if (/^[A-Za-z0-9_-]{1,128}$/.test(input.source)) {\n const byId = await library.ref.doc(input.source).get()\n if (byId.exists) rows.push(mediaRowOf(byId, library.org))\n }\n const byName = await library.ref.where('fileName', '==', input.source).limit(5).get()\n for (const doc of byName.docs) if (!rows.some((row) => row.id === doc.id)) rows.push(mediaRowOf(doc, library.org))\n }\n return rows\n}\n\n/**\n * The image a setting names, chosen from the rows read: a public image of\n * this site's library, or of its org's that this site may use, never deleted.\n * Its id wins over a file name; two images of one name are refused in words.\n */\nexport function aiSettingsMediaOf(\n arg: AiSettingsMediaArg,\n source: string,\n rows: readonly AiSettingsMediaRow[],\n hostId: string,\n): AiSettingsMedia | { error: string } {\n const noun = MEDIA_ARG_NOUNS[arg]\n const usable = rows.filter(\n (row) =>\n !row.deleted &&\n !row.private &&\n row.contentType.startsWith('image/') &&\n (row.visibleTo === undefined || visibleToHost(row.visibleTo, hostId)),\n )\n const byId = usable.find((row) => row.id === source)\n const named = usable.filter((row) => row.fileName === source)\n if (!byId && named.length > 1) return { error: `The media library has more than one image called “${source}”; name the one for the ${noun}.` }\n const row = byId ?? named[0]\n if (!row) return { error: `The media library has no image “${source}” this site can use for the ${noun}.` }\n const src = mediaNodeSrc({ url: row.url, cdnPath: row.cdnPath, contentHash: row.contentHash, private: row.private })\n if (!src) return { error: `The image “${source}” has no address the site can show.` }\n return { src, width: row.width, height: row.height, alt: row.alt, fileName: row.fileName || row.id }\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\n/** One change to the host document, with the sentence that says it was made. */\ninterface AiSettingsChange {\n path: string\n value: unknown\n said: string\n}\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\nconst sameValue = (a: unknown, b: unknown): boolean => JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n\n/**\n * The language change, as the Languages card would save it: a site with no\n * languages, or one, now publishes in this one; a multilingual site that\n * lists it makes it the default. A multilingual site that does not list it\n * is refused, because adding a language is the Languages card's decision.\n */\nexport function aiSettingsLanguageChanges(\n host: { locales?: unknown; defaultLocale?: unknown },\n language: string,\n): AiSettingsChange[] | { error: string } {\n const locales = Array.isArray(host.locales) ? host.locales.filter((one): one is string => typeof one === 'string') : []\n const said = `The site’s language is now ${language}.`\n if (locales.length > 1) {\n if (!locales.includes(language)) {\n return { error: `This site publishes in ${locales.join(', ')}; add ${language} in Settings → Details → Languages first.` }\n }\n return [{ path: 'defaultLocale', value: language, said }]\n }\n return [\n { path: 'locales', value: [language], said },\n { path: 'defaultLocale', value: language, said },\n ]\n}\n\n/** Every change the request asks of this host, before comparing with what is stored. */\nexport function aiSettingsChanges(\n host: Record<string, unknown>,\n request: AiSettingsRequest,\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>,\n): { changes: AiSettingsChange[]; problems: string[] } {\n const changes: AiSettingsChange[] = []\n const problems: string[] = []\n const set = (path: string, value: unknown, said: string) => changes.push({ path, value, said })\n if (request.name) set('displayName', request.name, `The site is now called “${request.name}”.`)\n if (request.seoTitle) set('seo.title', request.seoTitle, `The search title is now “${request.seoTitle}”.`)\n if (request.seoDescription) set('seo.description', request.seoDescription, 'The search description is set.')\n if (request.separator) set('seo.separator', request.separator, `The title separator is now “${request.separator}”.`)\n if (request.titlePattern) set('seo.titlePattern', request.titlePattern, `Untitled pages are now titled “${request.titlePattern}”.`)\n if (request.businessName) set('seo.entity.name', request.businessName, `The business name is now “${request.businessName}”.`)\n if (request.businessDescription) set('seo.entity.description', request.businessDescription, 'The business description is set.')\n if (request.telephone) set('seo.entity.telephone', request.telephone, `The phone number is now ${request.telephone}.`)\n if (request.supportEmail) set('business.supportEmail', request.supportEmail, `The contact email is now ${request.supportEmail}.`)\n if (request.address) set('business.address', request.address, 'The address is set.')\n if (request.timeZone) set('timeZone', request.timeZone, `The site’s time zone is now ${request.timeZone}.`)\n if (request.consentMode) {\n set('consent.mode', request.consentMode, request.consentMode === 'strict' ? 'The cookie banner now asks every visitor.' : 'The cookie banner now asks where the law requires.')\n }\n if (request.language) {\n const language = aiSettingsLanguageChanges(host, request.language)\n if ('error' in language) problems.push(language.error)\n else changes.push(...language)\n }\n if (media.favicon) set('seo.favicon', media.favicon.src, `The favicon is now “${media.favicon.fileName}”.`)\n if (media.logo) set('logoUrl', media.logo.src, `The logo is now “${media.logo.fileName}”.`)\n if (media.socialImage) {\n const image = media.socialImage\n const said = `The social image is now “${image.fileName}”.`\n set('seo.image', image.src, said)\n // Its size and alt, as the Social image card copies them at pick time.\n set('seo.imageWidth', image.width ?? 0, said)\n set('seo.imageHeight', image.height ?? 0, said)\n const savedAlt = getPath(host, 'seo.imageAlt')\n set('seo.imageAlt', inheritedMediaAlt({ placementAlt: savedAlt, assetAlt: image.alt }) ?? (typeof savedAlt === 'string' ? savedAlt : ''), said)\n }\n return { changes, problems }\n}\n\nexport type AiSettingsWrite =\n | { ok: true; done: string[]; notDone: string[]; changed: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The settings written, in one transaction that reads the host as it is\n * stored NOW: the member still holds a write role, and only the values that\n * differ from what is stored are written, with a site-wide publish outbox\n * entry beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteSettings(\n firestore: Firestore,\n input: {\n hostId: string\n uid: string\n request: AiSettingsRequest\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>\n now: Date\n },\n): Promise<AiSettingsWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiSettingsWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_SETTINGS_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const { changes, problems } = aiSettingsChanges(data, input.request, input.media)\n const changed = changes.filter((change) => !sameValue(getPath(data, change.path), change.value))\n const done = [...new Set(changes.map((change) => change.said))]\n if (!changed.length) return { ok: true, done, notDone: problems, changed: [], outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value\n tx.update(hostRef, updates)\n // The announce, written down in the save's own transaction: a setting\n // has no address, so it asks for the whole site, as the console's does.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, notDone: problems, changed: changed.map((change) => change.path), outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's settings now, or `null`. */\nexport async function aiSettingsAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_SETTINGS_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_SETTINGS_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, and what was not done. */\nexport function aiSettingsOutputNote(done: readonly string[], notDone: readonly string[], language: boolean): string {\n // The build's own wording for an item with no draft state (AGL-3745).\n const did = done.length ? `${done.join(' ')} Live now, in ${AI_SETTINGS_WHERE}.` : 'Nothing needed changing.'\n const translate = language ? ' The pages keep the words they have: changing the language does not translate them.' : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${translate}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobSettingsStepDeps {\n readMedia?: typeof readAiSettingsMediaRows\n write?: typeof writeAiSiteSettings\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n syncMemberships?: typeof syncHostProjectionForMembers\n}\n\n/** Each media argument resolved to its image, or a sentence for each that cannot be. */\nasync function mediaOf(\n firestore: Firestore,\n input: { hostId: string; orgId: string; request: AiSettingsRequest },\n readMedia: typeof readAiSettingsMediaRows,\n): Promise<{ media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>; problems: string[] }> {\n const media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>> = {}\n const problems: string[] = []\n for (const arg of AI_SETTINGS_MEDIA_ARGS) {\n const source = input.request[arg]\n if (!source) continue\n const rows = await readMedia(firestore, { hostId: input.hostId, orgId: input.orgId, source })\n const found = aiSettingsMediaOf(arg, source, rows, input.hostId)\n if ('error' in found) problems.push(found.error)\n else media[arg] = found\n }\n return { media, problems }\n}\n\nexport function createAiJobSettingsStep(deps: AiJobSettingsStepDeps = {}): AiJobStepRunner {\n const readMedia = deps.readMedia ?? readAiSettingsMediaRows\n const write = deps.write ?? writeAiSiteSettings\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const syncMemberships = deps.syncMemberships ?? syncHostProjectionForMembers\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_SETTINGS_NO_SITE_COPY)\n const read = aiSettingsRequestOf(job.inputs)\n if (aiSettingsRequestEmpty(read.request)) return unspent([AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const found = await mediaOf(firestore, { hostId, orgId: job.orgId, request: read.request }, readMedia)\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, media: found.media, now })\n if (written.ok === false) return unspent(written.error)\n const notDone = [...read.problems, ...found.problems, ...written.notDone]\n if (!written.done.length) return unspent([AI_SETTINGS_NOTHING_COPY, ...notDone].join(' '))\n if (written.outboxId) {\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({ hostIds: [hostId], reason: 'AI build changed site settings' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n if (written.changed.includes('displayName')) {\n // The site switcher lists sites by the name members' projections carry.\n await syncMemberships(job.orgId, hostId).catch((error: unknown) => {\n console.error('ai settings: membership projection not synced', { hostId, jobId: job.$id, error })\n })\n }\n const output: AiJobOutput = {\n resource: 'settings',\n id: AI_SETTINGS_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site settings',\n note: aiSettingsOutputNote(written.done, notDone, Boolean(read.request.language)),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobSettingsStep = createAiJobSettingsStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiSettingsJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A settings change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one\n * setting this can change. Refused in those words before anything is written.\n */\nexport function createAiSettingsJobAdmission(deps: AiSettingsJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_SETTINGS_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiSettingsRequestOf(context.inputs as Record<string, unknown>)\n return aiSettingsRequestEmpty(read.request) ? { status: 400, error: [AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiSettingsJobAdmission = createAiSettingsJobAdmission()\n\n/** Registers the settings step and its door (AGL-3748). */\nexport function registerAiSettingsJob(): void {\n registerAiJobStep(AI_SETTINGS_KIND, runAiJobSettingsStep, { minimumMs: AI_JOB_SETTINGS_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_SETTINGS_KIND, aiSettingsJobAdmission)\n}\n\n"],"names":["supportedTimeZones","createResourceUid","inheritedMediaAlt","mediaNodeSrc","hostRoleCanWrite","visibleToHost","SCREEN_ROOT_PATH","normalizeLocaleTag","isSeoTitleVariable","dropPluginSiteCache","syncHostProjectionForMembers","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_SETTINGS_KIND","AI_JOB_SETTINGS_STEP_MINIMUM_MS","AI_SETTINGS_NO_SITE_COPY","AI_SETTINGS_ROLE_COPY","AI_SETTINGS_LOCKED_COPY","AI_SETTINGS_NOTHING_COPY","AI_SETTINGS_WHERE","AI_SETTINGS_OUTPUT_ID","AI_SETTINGS_LIMITS","name","seoTitle","seoDescription","separator","titlePattern","businessDescription","businessName","telephone","supportEmail","address","AI_SETTINGS_LOCALE_PATTERN","AI_SETTINGS_CONSENT_MODES","AI_SETTINGS_MEDIA_ARGS","MEDIA_ARG_NOUNS","favicon","logo","socialImage","AI_SETTINGS_ARGS_SCHEMA","type","properties","description","maxLength","language","timeZone","consentMode","enum","additionalProperties","text","value","replace","trim","EMAIL","TELEPHONE","PATTERN_VARIABLE","aiSettingsTitlePatternOk","pattern","test","matchAll","every","match","aiSettingsLocale","tag","aiSettingsRequestOf","inputs","request","problems","bounded","key","noun","length","push","locale","includes","slice","aiSettingsRequestEmpty","Object","keys","mediaRowOf","doc","org","data","number","id","fileName","String","contentType","url","cdnPath","contentHash","private","deleted","visibleTo","Array","isArray","width","height","alt","readAiSettingsMediaRows","firestore","input","libraries","ref","collection","hostId","orgId","rows","library","source","byId","get","exists","byName","where","limit","docs","some","row","aiSettingsMediaOf","arg","usable","filter","startsWith","undefined","find","named","error","src","getPath","path","split","reduce","at","sameValue","a","b","JSON","stringify","aiSettingsLanguageChanges","host","locales","one","said","join","aiSettingsChanges","media","changes","set","image","savedAlt","placementAlt","assetAlt","writeAiSiteSettings","hostRef","runTransaction","tx","ok","roles","uid","changed","change","done","Set","map","notDone","outboxId","updates","updatedAt","now","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","aiSettingsAccessRefusal","deps","locked","lockdown","staff","intent","aiSettingsOutputNote","did","translate","not","mediaOf","readMedia","found","createAiJobSettingsStep","write","dropCache","syncMemberships","job","modelFor","model","unspent","failure","read","orgData","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","delete","console","jobId","$id","output","resource","hostSubdomain","label","note","Boolean","outputs","runAiJobSettingsStep","createAiSettingsJobAdmission","ownerOf","context","status","owner","aiSettingsJobAdmission","registerAiSettingsJob","minimumMs"],"mappings":"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"}
@@ -43,9 +43,12 @@ import type { AiJobStepContext, AiJobStepRunner } from './ai-job-text-step';
43
43
  * (`aiPublishSitePosts`), as a person's Publish would.
44
44
  * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for
45
45
  * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's
46
- * `product` draft writer. The catalog proposes no price, so each takes
47
- * the writer's default price (Zach, 2026-10-08: "default a price",
48
- * AGL-3676) for the owner to change; the job's row says so. Since the
46
+ * `product` draft writer, at the price the catalog suggests for what it
47
+ * is (Zach, 2026-10-12: a bar of soap is not priced like a gift set),
48
+ * which code keeps in range (`aiCatalogPrice`); a product with no price
49
+ * in range takes the writer's default price (his 2026-10-08 "default a
50
+ * price", AGL-3676). Every price is the owner's to change, and the job's
51
+ * row says so. Since the
49
52
  * 2026-10-08 Ember & Wick start, whose store showed no product at all,
50
53
  * each is LISTED at once (`comingSoon`: written `active`). Each gets a
51
54
  * photo in its slot, a stock photo of its own words where the deployment
@@ -196,7 +199,8 @@ export declare function aiSiteProductsBriefLine(input?: AiSiteProductsInput): st
196
199
  /**
197
200
  * The products unit's runner: the `products` step's catalog for the store's
198
201
  * brief, then each proposed product, up to the most a site starts with,
199
- * written by the plugin that keeps products at its default price, listed. A
202
+ * written by the plugin that keeps products at its suggested price (else the
203
+ * store's default), listed. A
200
204
  * product past the plan's allowance stops the writing there, and the row
201
205
  * says how many were added; none at all is the allowance's review. A photo
202
206
  * the product's rules refuse is dropped and the product written without it;
@@ -270,8 +274,18 @@ export declare function aiSiteListings(input: {
270
274
  */
271
275
  export declare function aiSiteProductDescriptionWithoutGaps(description: string): string;
272
276
  /** What each product a start wrote says on the job's page (AGL-3676). */
273
- export declare const AI_SITE_PRODUCT_NOTE = "On your store at a starting price. Set its real price \u2014 and your own photo \u2014 in Products.";
277
+ export declare const AI_SITE_PRODUCT_NOTE = "On your store at a suggested price. Change its price \u2014 and add your own photo \u2014 in Products.";
274
278
  /** The note a store's products row carries once they are written (AGL-3676): the step left before it sells. */
275
- export declare const AI_SITE_PRODUCTS_PRICE_NOTE = "Your products are on your store at a starting price. Set their real prices in Products.";
279
+ export declare const AI_SITE_PRODUCTS_PRICE_NOTE = "Your products are on your store at suggested prices. Review and change them in Products.";
280
+ /**
281
+ * The price a start writes a product with (Zach, 2026-10-12): the catalog's
282
+ * suggestion as `aiCatalogPrice` keeps it, stated as the writer reads one
283
+ * (`priceUsd`); nothing when there is none in range, so the writer gives the
284
+ * store's default price. Never `priceUsd: null`, which would leave the
285
+ * product unpriced on purpose.
286
+ */
287
+ export declare function aiSiteProductPriceContent(price: unknown): {
288
+ priceUsd?: number;
289
+ };
276
290
  /** What a page of a blog or a store is told about the posts or products built before it. */
277
291
  export declare function aiSiteContentBriefLines(outputs: readonly AiJobOutput[]): string[];
@@ -18,7 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
19
19
  import { pluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
20
20
  import { publishContentEntries, writeContentCollection, writeContentEntryDraft } from "@aglyn/tenant-data-admin/server/content-entry-drafts";
21
- import { aiProductsProposalOf } from "../model/ai-products.js";
21
+ import { aiCatalogPrice, aiProductsProposalOf } from "../model/ai-products.js";
22
22
  import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
23
23
  import { AI_SITE_BLOG_NAME, AI_SITE_BLOG_SLUGS, aiSiteWords } from "../model/ai-site-job.js";
24
24
  import { AI_STEP_TIERS } from "../providers/catalog.js";
@@ -52,9 +52,12 @@ import { aiGenerationSpent, aiUnspentOutcome } from "./ai-job-generation.js";
52
52
  * (`aiPublishSitePosts`), as a person's Publish would.
53
53
  * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for
54
54
  * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's
55
- * `product` draft writer. The catalog proposes no price, so each takes
56
- * the writer's default price (Zach, 2026-10-08: "default a price",
57
- * AGL-3676) for the owner to change; the job's row says so. Since the
55
+ * `product` draft writer, at the price the catalog suggests for what it
56
+ * is (Zach, 2026-10-12: a bar of soap is not priced like a gift set),
57
+ * which code keeps in range (`aiCatalogPrice`); a product with no price
58
+ * in range takes the writer's default price (his 2026-10-08 "default a
59
+ * price", AGL-3676). Every price is the owner's to change, and the job's
60
+ * row says so. Since the
58
61
  * 2026-10-08 Ember & Wick start, whose store showed no product at all,
59
62
  * each is LISTED at once (`comingSoon`: written `active`). Each gets a
60
63
  * photo in its slot, a stock photo of its own words where the deployment
@@ -411,7 +414,8 @@ export const runAiSitePostsUnit = createAiSitePostsRunner();
411
414
  /**
412
415
  * The products unit's runner: the `products` step's catalog for the store's
413
416
  * brief, then each proposed product, up to the most a site starts with,
414
- * written by the plugin that keeps products at its default price, listed. A
417
+ * written by the plugin that keeps products at its suggested price (else the
418
+ * store's default), listed. A
415
419
  * product past the plan's allowance stops the writing there, and the row
416
420
  * says how many were added; none at all is the allowance's review. A photo
417
421
  * the product's rules refuse is dropped and the product written without it;
@@ -464,7 +468,7 @@ export const runAiSitePostsUnit = createAiSitePostsRunner();
464
468
  /** The product's rules refusing what we made: ours, never the workspace's (AGL-3676). */ let rejected = null;
465
469
  for (const [index, product] of proposed.entries()){
466
470
  var _photos_index, _context_org, _catalog_output_hostSubdomain;
467
- const content = {
471
+ const content = _extends({
468
472
  name: product.name,
469
473
  type: product.type,
470
474
  // Listed at once, so no "[gift card terms]" gap reaches a shopper:
@@ -477,7 +481,7 @@ export const runAiSitePostsUnit = createAiSitePostsRunner();
477
481
  seoTitle: product.seoTitle,
478
482
  seoDescription: product.seoDescription,
479
483
  comingSoon: true
480
- };
484
+ }, aiSiteProductPriceContent(product.price));
481
485
  // A photo we picked that the product's rules refuse is dropped, never
482
486
  // the product: its slot stays empty for the owner's own.
483
487
  const photo = (_photos_index = photos[index]) != null ? _photos_index : null;
@@ -506,9 +510,9 @@ export const runAiSitePostsUnit = createAiSitePostsRunner();
506
510
  now: context.now,
507
511
  id: aiSiteProductId(job.$id, index),
508
512
  name: product.name,
509
- // No price stated: the writer gives it the store's default price
510
- // for the owner to change (AGL-3676). Listed at once. Its photo
511
- // slot holds a stock or starter photo for the owner to replace.
513
+ // Its suggested price, else the store's default, for the owner to
514
+ // change (AGL-3676). Listed at once. Its photo slot holds a stock or
515
+ // starter photo for the owner to replace.
512
516
  content: photoRefused ? content : withPhoto
513
517
  });
514
518
  if (written.ok === false) {
@@ -749,8 +753,20 @@ export const runAiSitePostsUnit = createAiSitePostsRunner();
749
753
  */ export function aiSiteProductDescriptionWithoutGaps(description) {
750
754
  return description.split('\n').map((line)=>line.split(/(?<=[.!?])\s+/).filter((sentence)=>!/\[[^\]]+\]/.test(sentence)).join(' ')).join('\n').replace(/\n{3,}/g, '\n\n').trim();
751
755
  }
752
- /** What each product a start wrote says on the job's page (AGL-3676). */ export const AI_SITE_PRODUCT_NOTE = 'On your store at a starting price. Set its real price — and your own photo — in Products.';
753
- /** The note a store's products row carries once they are written (AGL-3676): the step left before it sells. */ export const AI_SITE_PRODUCTS_PRICE_NOTE = 'Your products are on your store at a starting price. Set their real prices in Products.';
756
+ /** What each product a start wrote says on the job's page (AGL-3676). */ export const AI_SITE_PRODUCT_NOTE = 'On your store at a suggested price. Change its price — and add your own photo — in Products.';
757
+ /** The note a store's products row carries once they are written (AGL-3676): the step left before it sells. */ export const AI_SITE_PRODUCTS_PRICE_NOTE = 'Your products are on your store at suggested prices. Review and change them in Products.';
758
+ /**
759
+ * The price a start writes a product with (Zach, 2026-10-12): the catalog's
760
+ * suggestion as `aiCatalogPrice` keeps it, stated as the writer reads one
761
+ * (`priceUsd`); nothing when there is none in range, so the writer gives the
762
+ * store's default price. Never `priceUsd: null`, which would leave the
763
+ * product unpriced on purpose.
764
+ */ export function aiSiteProductPriceContent(price) {
765
+ const kept = aiCatalogPrice(price);
766
+ return kept === null ? {} : {
767
+ priceUsd: kept
768
+ };
769
+ }
754
770
  /** What a page of a blog or a store is told about the posts or products built before it. */ export function aiSiteContentBriefLines(outputs) {
755
771
  const posts = outputs.filter((output)=>output.resource === 'entry');
756
772
  const products = outputs.filter((output)=>output.resource === 'product' && !output.proposal);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-content.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 { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n publishContentEntries,\n writeContentCollection,\n writeContentEntryDraft,\n} from '@aglyn/tenant-data-admin/server/content-entry-drafts'\nimport { aiProductsProposalOf } from '../model/ai-products'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { AI_SITE_BLOG_NAME, AI_SITE_BLOG_SLUGS, aiSiteWords } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport {\n AI_BLOG_POST_MAX_TOKENS,\n AI_BLOG_POST_STEP,\n generateAiBlogPost,\n} from '../runtime/ai-blog-post-generation'\nimport {\n aiLayoutStarterPhotos,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n} from '../layout-language/ai-layout-pictures'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiLayoutStockPhotoSource } from './ai-layout-stock-photos'\nimport {\n aiLayoutListingId,\n aiLayoutListingPlacements,\n type AiLayoutListing,\n type AiLayoutListingScreen,\n} from '../layout-language/ai-layout-listings'\nimport { aiJobAdmissionFor, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\n\n/**\n * A GUIDED START'S FIRST POSTS AND FIRST PRODUCTS (AGL-3676).\n *\n * A site of the kind \"Blog & writing\" or \"Online store\" got the right look\n * and pages, and nothing real in them: the writing page's cards and the\n * shop's grid named posts and products nobody had written. On a paid plan,\n * the scaffold now builds one more part for those two kinds, before the\n * pages, each its own row with its own timer and credits:\n *\n * - WRITING YOUR FIRST POSTS: a content collection \"Blog\" — the platform's\n * own blog, served at `/{slug}` with an entry page per post — and\n * `AI_SITE_POSTS` posts in it, one generation a pass. Each is written as a\n * draft through the same rules the console's routes hold\n * (`content-entry-drafts.ts`), bylined with the business's name the person\n * gave, and given a cover: a stock photo of its own title where the\n * deployment has a library, else a starter (AGL-3676), which the blog's\n * cards show. They go live with the\n * site: the guided start's publish publishes them with its pages\n * (`aiPublishSitePosts`), as a person's Publish would.\n * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for\n * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's\n * `product` draft writer. The catalog proposes no price, so each takes\n * the writer's default price (Zach, 2026-10-08: \"default a price\",\n * AGL-3676) for the owner to change; the job's row says so. Since the\n * 2026-10-08 Ember & Wick start, whose store showed no product at all,\n * each is LISTED at once (`comingSoon`: written `active`). Each gets a\n * photo in its slot, a stock photo of its own words where the deployment\n * has a library, else a starter, for the owner to replace with their\n * own.\n *\n * The pages built after them are told their titles or names, and the\n * sections that show them list the real records (`aiSiteListings`,\n * `ai-layout-listings.ts`): the shop's Product grid, the blog's posts.\n *\n * ── Not on the Free taste ────────────────────────────────────────────────\n *\n * A guided start's Free site, built on an empty site with its layout and\n * form, leaves 18 credits once its eight sections and its retried page's\n * room are held (`aiFreeSiteSectionsWithin`, AGL-3660), under the 23 one post\n * costs at its worst; and a Free plan includes neither\n * the `commerce` feature nor a product (`productsPerHost` is 0).\n * `ai-job-free-site.spec.ts` shows the arithmetic. Free sites keep the pages\n * their kind already describes; the owner adds posts and products after.\n */\n\n/** How many first posts a paid blog gets. */\nexport const AI_SITE_POSTS = 3\n\n/** How many first products a paid store gets: asked for, and the most written. */\nexport const AI_SITE_PRODUCTS = { min: 3, max: 6 } as const\n\n/** What a products row says when none of what we made could be written: our failure, refunded. */\nexport const AI_SITE_PRODUCTS_NOT_WRITTEN_COPY = 'Your first products could not be added this time.'\n\n/** What each part's row reads. */\nexport const AI_SITE_POSTS_LABEL = 'Writing your first posts'\nexport const AI_SITE_PRODUCTS_LABEL = 'Adding your first products'\n\n/** The blog the posts go in, and the addresses it may answer at (`model/ai-site-job.ts`). */\nexport { AI_SITE_BLOG_NAME, AI_SITE_BLOG_SLUGS } from '../model/ai-site-job'\n\n/** The input a site unit's own job carries what the part needs in. */\nexport const AI_SITE_CONTENT_INPUT = 'siteContent'\n\n/** Which part a site of these answers gets; `null` for the Free taste and every other kind. */\nexport function aiSiteContentPart(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n freeTaste: boolean,\n): 'posts' | 'products' | null {\n if (freeTaste) return null\n const kind = aiSiteKindOfInputs(inputs)?.id\n if (kind === 'blog') return 'posts'\n if (kind === 'store') return 'products'\n return null\n}\n\n/**\n * ASSUMED: what one post pass reads and writes beside its generation: the\n * host, the collection's slug claim and the entry's transaction.\n */\nexport const AI_SITE_POST_WRITES_MS = 1_500\n\n/** One post's pass: its generation and re-ask at the `copy.blog` ceiling, and its writes. */\nexport const AI_SITE_POST_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS[AI_BLOG_POST_STEP],\n maxTokens: AI_BLOG_POST_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_SITE_POST_WRITES_MS,\n})\n\n/** What a posts unit's job is told: the posts already written and the addresses the blog must not take. */\nexport interface AiSitePostsInput {\n written: Array<{ id: string; title: string }>\n total: number\n /** Addresses the site's own pages answer at, which the blog must not shadow. */\n avoidSlugs: string[]\n /** Who the posts are by: the business's name as given, else empty. */\n byline: string\n}\n\n/** What a products unit's job is told. */\nexport interface AiSiteProductsInput {\n min: number\n max: number\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction postsInputOf(job: Pick<AiJob, 'inputs'>): AiSitePostsInput {\n const raw = (job.inputs?.[AI_SITE_CONTENT_INPUT] ?? {}) as Partial<AiSitePostsInput>\n return {\n written: Array.isArray(raw.written) ? raw.written.filter((one) => one && typeof one.id === 'string') : [],\n total: typeof raw.total === 'number' && raw.total > 0 ? Math.floor(raw.total) : AI_SITE_POSTS,\n avoidSlugs: Array.isArray(raw.avoidSlugs) ? raw.avoidSlugs.map(str).filter(Boolean) : [],\n byline: str(raw.byline),\n }\n}\n\n/** The id of the `index`th post, of the blog its unit writes under `unitJobId`. */\nexport function aiSitePostId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** The id of the `index`th product a products unit writes. */\nexport function aiSiteProductId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** A site's subdomain and name, as its outputs and its posts' byline read them. */\nasync function readSite(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<{ subdomain: string | null; name: string } | null> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n if (!host.exists) return null\n const seo = (host.get('seo') ?? {}) as Record<string, unknown>\n const subdomain = str(host.get('subdomain')) || null\n return { subdomain, name: str(host.get('displayName')) || str(seo['title']) }\n}\n\nconst limitReview = (message: string) => ({ reason: 'limit' as const, message, findings: [] })\n\nexport interface AiSitePostsRunnerDeps {\n generate?: typeof generateAiBlogPost\n writeCollection?: typeof writeContentCollection\n writeEntry?: typeof writeContentEntryDraft\n /** Where each post's cover comes from; the stock library, else the starters, otherwise. */\n cover?: typeof aiSitePostCover\n}\n\n/** A post's cover's shape: the blog's cards' (`COLLECTION_LIST_COVER_RATIO`, the posts listing's). */\nconst AI_SITE_POST_COVER_ASPECT = 3 / 2\n\n/**\n * A post's cover (AGL-3676): a stock photo of the post's own title, copied\n * into the site's library, where the deployment has a stock library; else the\n * starter photo a post was always given, turned by the job so blogs differ.\n * The blog's cards and the post's page show it, for the owner to replace.\n * Never throws: a post whose search fails takes its starter.\n */\nexport async function aiSitePostCover(input: {\n job: Pick<AiJob, '$id' | 'hostId' | 'createdBy' | 'inputs' | 'brief'>\n title: string\n index: number\n total: number\n signal?: AbortSignal\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}): Promise<string | null> {\n const { job, index } = input\n if (job.hostId && input.title.trim()) {\n try {\n const source = (input.stockPhotos ?? aiLayoutStockPhotoSource)({\n hostId: job.hostId,\n uid: job.createdBy,\n seed: `${job.$id}:posts:${index}`,\n business: aiSiteWords(job.inputs).about || job.brief,\n sectionNames: ['Blog'],\n jobId: aiOriginJobId(job),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const [found] = source\n ? await source([\n {\n imageId: `cover${index}`,\n frameId: null,\n iconId: null,\n alt: input.title,\n aspect: AI_SITE_POST_COVER_ASPECT,\n sectionIndex: 0,\n role: 'gallery' as const,\n },\n ])\n : []\n if (found?.src) return found.src\n } catch (error) {\n console.warn('ai site posts: the stock photo failed; the starter covers it', { error: String(error) })\n }\n }\n const starters = aiLayoutStarterPhotos(\n Array.from({ length: Math.max(input.total, index + 1) }, (_, slot) => ({\n imageId: `cover${slot}`,\n frameId: null,\n iconId: null,\n alt: '',\n aspect: 16 / 9,\n sectionIndex: slot + 1,\n role: 'gallery' as const,\n })),\n job.$id,\n )\n return starters[index]?.src ?? null\n}\n\n/**\n * The posts unit's runner, on the job the scaffold derived for it: one post a\n * pass, continuing while posts remain. Its first pass makes the blog; every\n * pass finds it again under the unit's own id, and a post under its own id,\n * so a pass repeated after its write reports the post it already wrote.\n */\nexport function createAiSitePostsRunner(deps: AiSitePostsRunnerDeps = {}): AiJobStepRunner {\n const generate = deps.generate ?? generateAiBlogPost\n const writeCollection = deps.writeCollection ?? writeContentCollection\n const writeEntry = deps.writeEntry ?? writeContentEntryDraft\n const coverFor = deps.cover ?? aiSitePostCover\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, signal } = context\n const model = context.modelFor?.(AI_BLOG_POST_STEP) ?? aiModelForStep(AI_BLOG_POST_STEP)\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'Open the site before writing its posts.' }\n const input = postsInputOf(job)\n const index = input.written.length\n if (index >= input.total) return aiUnspentOutcome(model)\n const site = await readSite(firestore, job.hostId)\n if (!site) return { ...aiUnspentOutcome(model), failure: 'This site no longer exists.' }\n const avoid = new Set(input.avoidSlugs.map((slug) => slug.toLowerCase()))\n const blog = await writeCollection(firestore, {\n hostId: job.hostId,\n uid: job.createdBy,\n id: job.$id,\n displayName: AI_SITE_BLOG_NAME,\n slugs: AI_SITE_BLOG_SLUGS.filter((slug) => !avoid.has(slug)),\n now: context.now,\n })\n if (blog.ok === false) return aiUnspentOutcome(model, { review: limitReview(blog.error) })\n\n const generation = await generate({\n brief: job.brief,\n merchantWords: job.brief,\n earlierTitles: input.written.map((post) => post.title),\n index: index + 1,\n total: input.total,\n model,\n maxTokens: AI_SITE_POST_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const post = generation.value\n // A stock photo of its title as its cover, else a starter (AGL-3676).\n const cover = await coverFor({\n job,\n title: post.title,\n index,\n total: input.total,\n ...(signal ? { signal } : {}),\n }).catch(() => null)\n const byline = input.byline || site.name\n const written = await writeEntry(firestore, {\n hostId: job.hostId,\n collectionId: blog.id,\n uid: job.createdBy,\n id: aiSitePostId(job.$id, index),\n content: {\n title: post.title,\n excerpt: post.excerpt,\n body: post.body,\n seoDescription: post.seoDescription,\n ...(cover ? { coverImage: cover, coverImageAlt: post.title } : {}),\n ...(byline ? { authorName: byline } : {}),\n },\n now: context.now,\n })\n if (written.ok === false) return { ...spent, review: limitReview(written.error) }\n const output: AiJobOutput = {\n resource: 'entry',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: site.subdomain,\n label: written.title,\n note: `A post in ${blog.displayName}, at /${blog.slug}/${written.slug}. It goes live with the site.`,\n proposal: { collectionId: blog.id, collectionSlug: blog.slug, slug: written.slug },\n }\n return { ...spent, outputs: [output], ...(index + 1 < input.total ? { continue: true } : {}) }\n }\n}\n\nexport const runAiSitePostsUnit = createAiSitePostsRunner()\n\nexport interface AiSiteProductsRunnerDeps {\n /** The catalog's runner: the `products` step's. */\n catalog: AiJobStepRunner\n writerFor?: typeof pluginResourceDraftWriter\n /** Where each product's photo comes from; the stock library, else the starters, otherwise. */\n photos?: typeof aiSiteProductPhotos\n}\n\n/**\n * A photo for each product (AGL-3676), in order: a stock photo of the\n * product's own words, copied into the site's library, where the deployment\n * has a stock library; else a starter photo, as a page's empty picture takes\n * one. It fills the photo slot every product card and product page has, for\n * the owner to replace with their own; the product's note says so. Never\n * throws: a product with no photo is still written.\n */\nexport async function aiSiteProductPhotos(input: {\n job: Pick<AiJob, '$id' | 'hostId' | 'createdBy' | 'inputs' | 'brief'>\n names: readonly string[]\n /** What each product's photo should show, as its catalog proposed it, by position (AGL-3676). */\n shows?: readonly string[]\n signal?: AbortSignal\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}): Promise<Array<string | null>> {\n if (!input.names.length || !input.job.hostId) return input.names.map(() => null)\n const slots: AiLayoutPictureSlot[] = input.names.map((name, index) => ({\n imageId: `product${index}`,\n frameId: null,\n iconId: null,\n alt: name,\n // The product card's own shape (`cardStyle: 'photo'`).\n aspect: 4 / 5,\n sectionIndex: 0,\n role: 'gallery' as const,\n // Searched for by its own name, then what its photo shows, with the shop's\n // category, and held to that category (AGL-3676).\n product: { subjects: [name, input.shows?.[index] ?? ''].filter((words) => words.trim()) },\n }))\n const seed = `${input.job.$id}:products`\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n try {\n const source = (input.stockPhotos ?? aiLayoutStockPhotoSource)({\n hostId: input.job.hostId,\n uid: input.job.createdBy,\n seed,\n business: aiSiteWords(input.job.inputs).about || input.job.brief,\n sectionNames: ['Products'],\n jobId: aiOriginJobId(input.job),\n // Each product may try its name, its photo's subject, its category, a\n // gift's wrapper and the craft's broad searches (AGL-3660).\n searches: slots.length * 6,\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (source) found = await source(slots)\n } catch (error) {\n console.warn('ai site products: the stock photos failed; starter photos fill them', { error: String(error) })\n }\n const starters = aiLayoutStarterPhotos(slots, seed)\n const hostId = input.job.hostId\n // Every product has a photo (AGL-3676): a starter where no stock photo\n // named its category, never an empty tile.\n return slots.map(\n (_slot, index) =>\n aiSiteProductPhotoSrc(found[index]?.src, hostId) ?? aiSiteProductPhotoSrc(starters[index]?.src, hostId),\n )\n}\n\n/**\n * A photo as a product stores it: the form the console's picker writes when\n * an owner picks one from the library (`console-media-picker-provider`), the\n * asset's CDN path `/api/media/cdn/{scope}/{mediaId}`, or a starter's own\n * site path. A page's picture slot holds the library's `media:` reference\n * (`mediaNodeSrc`), which is what the stock source hands back, and the\n * product's rules take only an https address or a path on the site: the\n * candle store's guided start of 2026-10-09 (job FYNasg1h0S) wrote no\n * product at all because every photo was a `media:` reference. The path\n * names the asset, not its bytes, so a replace in the library still reaches\n * the product. Anything else is no photo, never a value the product refuses.\n */\nexport function aiSiteProductPhotoSrc(src: string | null | undefined, hostId: string): string | null {\n if (!src) return null\n const path = resolveMediaSrc(src, { hostId })\n if (!path || path.length > 2_000 || /[\\s\"'<>]/.test(path)) return null\n return path.startsWith('/') || path.startsWith('https://') ? path : null\n}\n\n/**\n * A proposed product's real choices (AGL-3676): each option with two or more\n * distinct values. An option of one value is no choice for a shopper to make\n * — the beta.237 Willow Wick gift set's \"Scent\" select offered nothing — so\n * it is left out, and its one value stays in the product's own words.\n */\nexport function aiSiteProductChoices(\n options: ReadonlyArray<{ name: string; values: readonly string[] }> | null | undefined,\n): Array<{ name: string; values: string[] }> {\n return (options ?? []).flatMap((option) => {\n const seen = new Set<string>()\n const values = option.values\n .map((value) => value.trim())\n .filter((value) => value && !seen.has(value.toLowerCase()) && !!seen.add(value.toLowerCase()))\n return option.name.trim() && values.length >= 2 ? [{ name: option.name.trim(), values }] : []\n })\n}\n\n/** The sentence the products unit's brief ends with, which the catalog's rules read as how many. */\nexport function aiSiteProductsBriefLine(input: AiSiteProductsInput = AI_SITE_PRODUCTS): string {\n return `Propose between ${input.min} and ${input.max} products: the store's first ones.`\n}\n\n/**\n * The products unit's runner: the `products` step's catalog for the store's\n * brief, then each proposed product, up to the most a site starts with,\n * written by the plugin that keeps products at its default price, listed. A\n * product past the plan's allowance stops the writing there, and the row\n * says how many were added; none at all is the allowance's review. A photo\n * the product's rules refuse is dropped and the product written without it;\n * a product they refuse outright is skipped, and none written for that\n * reason is our failure, refunded, never the person's review (AGL-3676).\n */\nexport function createAiSiteProductsRunner(deps: AiSiteProductsRunnerDeps): AiJobStepRunner {\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n const keeper = writerFor('product')\n const model = context.modelFor?.('job.products') ?? aiModelForStep('job.products')\n if (!keeper || !job.hostId) return { ...aiUnspentOutcome(model), failure: 'Products are not available on this site.' }\n const outcome = await deps.catalog({\n ...context,\n job: { ...job, kind: 'products', inputs: { ...job.inputs, target: 'catalog' } },\n })\n if (outcome.refused || outcome.failure || outcome.review) return outcome\n const catalog = outcome.outputs.map((output) => ({ output, proposal: aiProductsProposalOf(output) })).find(\n (entry) => entry.proposal?.kind === 'catalog',\n )\n if (!catalog || catalog.proposal?.kind !== 'catalog') return { ...outcome, outputs: [] }\n const proposed = catalog.proposal.products.slice(0, AI_SITE_PRODUCTS.max)\n const photos = await (deps.photos ?? aiSiteProductPhotos)({\n job,\n names: proposed.map((product) => product.name),\n shows: proposed.map((product) => product.photo ?? ''),\n ...(context.signal ? { signal: context.signal } : {}),\n }).catch(() => proposed.map(() => null))\n const outputs: AiJobOutput[] = []\n let stopped: string | null = null\n /** The product's rules refusing what we made: ours, never the workspace's (AGL-3676). */\n let rejected: string | null = null\n for (const [index, product] of proposed.entries()) {\n const content: Record<string, unknown> = {\n name: product.name,\n type: product.type,\n // Listed at once, so no \"[gift card terms]\" gap reaches a shopper:\n // a sentence that leaves a fact for the owner is left out.\n description: aiSiteProductDescriptionWithoutGaps(product.description),\n tags: product.tags,\n // A choice is two values or more (AGL-3676): a \"Scent\" of one value\n // drew a select with nothing to pick on the live Willow Wick page.\n options: aiSiteProductChoices(product.options),\n seoTitle: product.seoTitle,\n seoDescription: product.seoDescription,\n comingSoon: true,\n }\n // A photo we picked that the product's rules refuse is dropped, never\n // the product: its slot stays empty for the owner's own.\n const photo = photos[index] ?? null\n const withPhoto = photo ? { ...content, mediaUrls: [photo] } : content\n const photoRefused =\n photo !== null &&\n keeper.writer.check(withPhoto, { hostId: job.hostId }).ok === false &&\n keeper.writer.check(content, { hostId: job.hostId }).ok\n if (photoRefused) {\n console.warn('ai site products: the product refused its photo; written without it', {\n orgId: job.orgId,\n jobId: job.$id,\n photo,\n })\n }\n const written = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: aiSiteProductId(job.$id, index),\n name: product.name,\n // No price stated: the writer gives it the store's default price\n // for the owner to change (AGL-3676). Listed at once. Its photo\n // slot holds a stock or starter photo for the owner to replace.\n content: photoRefused ? content : withPhoto,\n })\n if (written.ok === false) {\n // A 400 is the product's rules refusing content we made: that one\n // product is skipped and the rest are still written. Anything else\n // (the allowance, the role, the site) stops the writing there.\n if (written.status === 400) {\n console.error('ai site products: the product writer refused a product we made', {\n orgId: job.orgId,\n jobId: job.$id,\n index,\n error: written.error,\n })\n rejected = written.error\n continue\n }\n stopped = written.error\n break\n }\n outputs.push({\n resource: 'product',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: catalog.output.hostSubdomain ?? null,\n label: written.name,\n note: AI_SITE_PRODUCT_NOTE,\n })\n }\n if (!outputs.length) {\n // Nothing written because the product's rules refused what we made:\n // the failure is ours, so the item's credits are refunded\n // (`aiUnitFailure` → `ours: true`). Only the workspace's allowance,\n // role or site is the person's to review.\n if (!stopped && rejected) return { ...outcome, outputs: [], failure: AI_SITE_PRODUCTS_NOT_WRITTEN_COPY }\n return { ...outcome, outputs: [], review: limitReview(stopped ?? 'No product could be added.') }\n }\n // The allowance that stopped the rest is said where the last one added is.\n const last = outputs[outputs.length - 1]\n if (stopped) {\n last.note = `${last.note} ${outputs.length} of ${proposed.length} added: ${stopped}.`\n } else if (outputs.length < proposed.length) {\n last.note = `${last.note} ${outputs.length} of ${proposed.length} added.`\n }\n return { ...outcome, outputs }\n }\n}\n\n/**\n * Whether a part may be built for this member on this site, asked before its\n * first pass so a refusal spends nothing; `null` admits. Products ask what a\n * `products` job asks (the plan's commerce feature, Commerce on and released\n * for the site) and the writer's own refusal (role, allowance); posts ask\n * that the member may write the site's content.\n */\nexport async function aiSiteContentRefusal(\n part: 'posts' | 'products',\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy' | 'inputs'> },\n deps: { writerFor?: typeof pluginResourceDraftWriter; admissionFor?: typeof aiJobAdmissionFor } = {},\n): Promise<string | null> {\n const { job, firestore } = context\n if (!job.hostId) return 'Open the site first.'\n if (part === 'posts') {\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n return hostRoleCanWrite((host.get('memberRoles') ?? {})[job.createdBy]) ? null : 'Editing requires the editor role'\n }\n const admission = (deps.admissionFor ?? aiJobAdmissionFor)('products')\n const refused: AiJobAdmissionRefusal | null = admission\n ? await admission({\n firestore,\n orgId: job.orgId,\n hostId: job.hostId,\n inputs: { target: 'catalog' },\n org: context.org ?? null,\n uid: job.createdBy,\n })\n : { status: 403, error: 'Products are not available on this site.' }\n if (refused) return refused.error\n const keeper = (deps.writerFor ?? pluginResourceDraftWriter)('product')\n if (!keeper) return 'Products are not available on this site.'\n const room = await keeper.writer.refusal({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n })\n return room?.error ?? null\n}\n\n/** The guided start's posts, published with its pages (AGL-3676). */\nexport interface AiSitePostsPublish {\n published: number\n kept: number\n /** The blog's own addresses, for the publish's cache drop. */\n paths: string[]\n}\n\n/**\n * Publishes the posts a guided start wrote, as the entry editor's Publish\n * does, once its pages are live. Never throws: a post that stays a draft is\n * the owner's to publish from Content.\n */\nexport async function aiPublishSitePosts(\n firestore: FirebaseFirestore.Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'createdBy'>; outputs: readonly AiJobOutput[]; now: Date },\n publish: typeof publishContentEntries = publishContentEntries,\n): Promise<AiSitePostsPublish | null> {\n const posts = input.outputs.filter((output) => output.resource === 'entry')\n const collectionId = str(posts[0]?.proposal?.['collectionId'])\n const collectionSlug = str(posts[0]?.proposal?.['collectionSlug'])\n if (!input.job.hostId || !posts.length || !collectionId) return null\n try {\n const result = await publish(firestore, {\n hostId: input.job.hostId,\n collectionId,\n uid: input.job.createdBy,\n ids: posts.map((post) => post.id),\n now: input.now,\n })\n if ('ok' in result) {\n console.warn('ai site posts not published', { jobId: input.job.$id, error: result.error })\n return { published: 0, kept: posts.length, paths: [] }\n }\n const slugs = posts\n .filter((post) => result.published.includes(post.id))\n .map((post) => str(post.proposal?.['slug']))\n .filter(Boolean)\n return {\n published: result.published.length,\n kept: result.kept.length,\n paths: collectionSlug ? [`/${collectionSlug}`, ...slugs.map((slug) => `/${collectionSlug}/${slug}`)] : [],\n }\n } catch (error) {\n console.error('ai site posts publish threw', { orgId: input.job.orgId, jobId: input.job.$id, error })\n return null\n }\n}\n\n/**\n * The site's listings a layout or a page unit is handed (AGL-3676,\n * `ai-layout-listings.ts`): the store's catalog once its products are written,\n * the blog once its posts are, each with the sections of the plan's pages\n * that list it. A layout is built before either, so it is told the store will\n * sell (`sells`, its ledger still owing the products part), which puts the\n * cart in its header.\n */\nexport function aiSiteListings(input: {\n outputs: readonly AiJobOutput[]\n screens: readonly AiLayoutListingScreen[]\n /** The ledger owes a store's first products: a layout built before them carries the cart. */\n sells?: boolean\n /**\n * The site is a store (`siteKind: 'store'`, AGL-3676): its Shop page and\n * its home list the catalog whatever its first products came to — the\n * live Hearth & Wick start's products step failed, and its Shop page was\n * six cards naming kinds of candle. An empty catalog says so in the grid.\n */\n store?: boolean\n /**\n * The site is a music site (`siteKind: 'music'`, AGL-3716): its Music page,\n * or its home, places an empty Music player for the artist's own tracks.\n * Nothing is sourced: the owner uploads the recordings.\n */\n music?: boolean\n}): AiLayoutListing[] {\n const listings: AiLayoutListing[] = []\n const products = input.outputs.filter((output) => output.resource === 'product' && !output.proposal)\n if (products.length || input.sells || input.store) {\n const contact = aiSiteContactPath(input.screens)\n listings.push({\n id: aiLayoutListingId('products'),\n kind: 'products',\n name: 'the shop',\n records: products.map((product) => product.label),\n ...(contact ? { emptyAction: { label: 'Get in touch', href: contact } } : {}),\n // A store whose first products were skipped or failed lists its\n // (empty) catalog, but its header carries no cart until it sells.\n ...(products.length || input.sells ? {} : { cart: false }),\n placements: aiLayoutListingPlacements('products', input.screens),\n })\n // A store that sells shows its customers' reviews and takes newsletter\n // sign-ups where its pages name them (AGL-3676): both commerce elements,\n // so only where the store's commerce runs, never on a store that cannot\n // sell yet.\n if (products.length || input.sells) {\n for (const kind of ['reviews', 'signup'] as const) {\n const placements = aiLayoutListingPlacements(kind, input.screens)\n if (placements.length) listings.push({ id: aiLayoutListingId(kind), kind, name: kind === 'reviews' ? 'the store' : 'the newsletter', records: [], placements })\n }\n }\n }\n const posts = input.outputs.filter((output) => output.resource === 'entry')\n const slug = str(posts[0]?.proposal?.['collectionSlug'])\n if (posts.length && slug) {\n listings.push({\n id: aiLayoutListingId('posts'),\n kind: 'posts',\n name: 'the blog',\n records: posts.map((post) => post.label),\n href: `/${slug}`,\n collectionSlug: slug,\n placements: aiLayoutListingPlacements('posts', input.screens),\n })\n }\n if (input.music) {\n const placements = aiLayoutListingPlacements('tracks', input.screens)\n if (placements.length) {\n listings.push({ id: aiLayoutListingId('tracks'), kind: 'tracks', name: 'the music', records: [], placements })\n }\n }\n return listings\n}\n\n/** The page a visitor gets in touch on, by its path: one whose address or name says contact, else the one placing a form. */\nfunction aiSiteContactPath(screens: readonly AiLayoutListingScreen[]): string | null {\n const path = (slug: string) => `/${slug.trim().replace(/^\\/+|\\/+$/g, '')}`\n const isPath = (slug: string) => /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/.test(path(slug))\n const named = screens.find((screen) => isPath(screen.slug) && /\\b(contact|get in touch|enquir|inquir)/i.test(`${screen.slug} ${screen.title}`))\n if (named) return path(named.slug)\n const form = screens.find((screen) => isPath(screen.slug) && screen.sections.some((section) => (section.uses ?? []).some((use) => /form/i.test(use))))\n return form ? path(form.slug) : null\n}\n\n/**\n * A proposed product's description with each sentence that leaves a gap for\n * the owner (\"Delivery details: [how and when the card is sent].\") taken\n * out (AGL-3676): a start's products are listed as soon as they are written,\n * and a live run's gift card showed its gaps on the product page. A line\n * break is kept; nothing else is changed.\n */\nexport function aiSiteProductDescriptionWithoutGaps(description: string): string {\n return description\n .split('\\n')\n .map((line) =>\n line\n .split(/(?<=[.!?])\\s+/)\n .filter((sentence) => !/\\[[^\\]]+\\]/.test(sentence))\n .join(' '),\n )\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** What each product a start wrote says on the job's page (AGL-3676). */\nexport const AI_SITE_PRODUCT_NOTE =\n 'On your store at a starting price. Set its real price — and your own photo — in Products.'\n\n/** The note a store's products row carries once they are written (AGL-3676): the step left before it sells. */\nexport const AI_SITE_PRODUCTS_PRICE_NOTE =\n 'Your products are on your store at a starting price. Set their real prices in Products.'\n\n/** What a page of a blog or a store is told about the posts or products built before it. */\nexport function aiSiteContentBriefLines(outputs: readonly AiJobOutput[]): string[] {\n const posts = outputs.filter((output) => output.resource === 'entry')\n const products = outputs.filter((output) => output.resource === 'product' && !output.proposal)\n const lines: string[] = []\n if (posts.length) {\n const slug = str(posts[0].proposal?.['collectionSlug'])\n lines.push(\n `This site's blog${slug ? ` at /${slug}` : ''} has these posts: ${posts.map((post) => `“${post.label}”`).join(', ')}.`,\n 'Where a page features writing, feature these posts by their titles; never name a post that is not in this list.',\n )\n }\n if (products.length) {\n lines.push(\n `This store's products are: ${products.map((product) => `“${product.label}”`).join(', ')}.`,\n 'Where a page features products, feature these by their names; never name another product, and never state a price.',\n // The collection tiles a storefront home shows (AGL-3676): its own range, grouped.\n 'Where a page shows the shop by collection, group these products into two to four collections by what they are (a kind, a use or an occasion), one item each, named for the group and never for one product.',\n )\n }\n return lines\n}\n"],"names":["resolveMediaSrc","hostRoleCanWrite","pluginResourceDraftWriter","publishContentEntries","writeContentCollection","writeContentEntryDraft","aiProductsProposalOf","aiSiteKindOfInputs","AI_SITE_BLOG_NAME","AI_SITE_BLOG_SLUGS","aiSiteWords","AI_STEP_TIERS","aiModelForStep","AI_BLOG_POST_MAX_TOKENS","AI_BLOG_POST_STEP","generateAiBlogPost","aiLayoutStarterPhotos","aiOriginJobId","aiLayoutStockPhotoSource","aiLayoutListingId","aiLayoutListingPlacements","aiJobAdmissionFor","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_SITE_POSTS","AI_SITE_PRODUCTS","min","max","AI_SITE_PRODUCTS_NOT_WRITTEN_COPY","AI_SITE_POSTS_LABEL","AI_SITE_PRODUCTS_LABEL","AI_SITE_CONTENT_INPUT","aiSiteContentPart","inputs","freeTaste","kind","id","AI_SITE_POST_WRITES_MS","AI_SITE_POST_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","trim","postsInputOf","job","raw","written","Array","isArray","filter","one","total","Math","floor","avoidSlugs","map","Boolean","byline","aiSitePostId","unitJobId","index","aiSiteProductId","readSite","firestore","hostId","host","collection","doc","get","exists","seo","subdomain","name","limitReview","message","reason","findings","AI_SITE_POST_COVER_ASPECT","aiSitePostCover","input","starters","title","source","stockPhotos","uid","createdBy","seed","$id","business","about","brief","sectionNames","jobId","signal","found","imageId","frameId","iconId","alt","aspect","sectionIndex","role","src","error","console","warn","String","from","length","_","slot","createAiSitePostsRunner","deps","generate","writeCollection","writeEntry","coverFor","cover","context","model","modelFor","failure","site","avoid","Set","slug","toLowerCase","blog","displayName","slugs","has","now","ok","review","generation","merchantWords","earlierTitles","post","spent","status","refused","catch","collectionId","content","excerpt","body","seoDescription","coverImage","coverImageAlt","authorName","output","resource","hostSubdomain","label","note","proposal","collectionSlug","outputs","continue","runAiSitePostsUnit","aiSiteProductPhotos","names","slots","product","subjects","shows","words","searches","_slot","aiSiteProductPhotoSrc","path","test","startsWith","aiSiteProductChoices","options","flatMap","option","seen","values","add","aiSiteProductsBriefLine","createAiSiteProductsRunner","writerFor","catalog","keeper","outcome","target","find","entry","proposed","products","slice","photos","photo","stopped","rejected","entries","type","description","aiSiteProductDescriptionWithoutGaps","tags","seoTitle","comingSoon","withPhoto","mediaUrls","photoRefused","writer","check","orgId","write","org","push","AI_SITE_PRODUCT_NOTE","last","aiSiteContentRefusal","part","admission","admissionFor","room","refusal","aiPublishSitePosts","publish","posts","result","ids","published","kept","paths","includes","aiSiteListings","listings","sells","store","contact","aiSiteContactPath","screens","records","emptyAction","href","cart","placements","music","replace","isPath","named","screen","form","sections","some","section","uses","use","split","line","sentence","join","AI_SITE_PRODUCTS_PRICE_NOTE","aiSiteContentBriefLines","lines"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-content.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 { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n publishContentEntries,\n writeContentCollection,\n writeContentEntryDraft,\n} from '@aglyn/tenant-data-admin/server/content-entry-drafts'\nimport { aiCatalogPrice, aiProductsProposalOf } from '../model/ai-products'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { AI_SITE_BLOG_NAME, AI_SITE_BLOG_SLUGS, aiSiteWords } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport {\n AI_BLOG_POST_MAX_TOKENS,\n AI_BLOG_POST_STEP,\n generateAiBlogPost,\n} from '../runtime/ai-blog-post-generation'\nimport {\n aiLayoutStarterPhotos,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n} from '../layout-language/ai-layout-pictures'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiLayoutStockPhotoSource } from './ai-layout-stock-photos'\nimport {\n aiLayoutListingId,\n aiLayoutListingPlacements,\n type AiLayoutListing,\n type AiLayoutListingScreen,\n} from '../layout-language/ai-layout-listings'\nimport { aiJobAdmissionFor, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\n\n/**\n * A GUIDED START'S FIRST POSTS AND FIRST PRODUCTS (AGL-3676).\n *\n * A site of the kind \"Blog & writing\" or \"Online store\" got the right look\n * and pages, and nothing real in them: the writing page's cards and the\n * shop's grid named posts and products nobody had written. On a paid plan,\n * the scaffold now builds one more part for those two kinds, before the\n * pages, each its own row with its own timer and credits:\n *\n * - WRITING YOUR FIRST POSTS: a content collection \"Blog\" — the platform's\n * own blog, served at `/{slug}` with an entry page per post — and\n * `AI_SITE_POSTS` posts in it, one generation a pass. Each is written as a\n * draft through the same rules the console's routes hold\n * (`content-entry-drafts.ts`), bylined with the business's name the person\n * gave, and given a cover: a stock photo of its own title where the\n * deployment has a library, else a starter (AGL-3676), which the blog's\n * cards show. They go live with the\n * site: the guided start's publish publishes them with its pages\n * (`aiPublishSitePosts`), as a person's Publish would.\n * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for\n * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's\n * `product` draft writer, at the price the catalog suggests for what it\n * is (Zach, 2026-10-12: a bar of soap is not priced like a gift set),\n * which code keeps in range (`aiCatalogPrice`); a product with no price\n * in range takes the writer's default price (his 2026-10-08 \"default a\n * price\", AGL-3676). Every price is the owner's to change, and the job's\n * row says so. Since the\n * 2026-10-08 Ember & Wick start, whose store showed no product at all,\n * each is LISTED at once (`comingSoon`: written `active`). Each gets a\n * photo in its slot, a stock photo of its own words where the deployment\n * has a library, else a starter, for the owner to replace with their\n * own.\n *\n * The pages built after them are told their titles or names, and the\n * sections that show them list the real records (`aiSiteListings`,\n * `ai-layout-listings.ts`): the shop's Product grid, the blog's posts.\n *\n * ── Not on the Free taste ────────────────────────────────────────────────\n *\n * A guided start's Free site, built on an empty site with its layout and\n * form, leaves 18 credits once its eight sections and its retried page's\n * room are held (`aiFreeSiteSectionsWithin`, AGL-3660), under the 23 one post\n * costs at its worst; and a Free plan includes neither\n * the `commerce` feature nor a product (`productsPerHost` is 0).\n * `ai-job-free-site.spec.ts` shows the arithmetic. Free sites keep the pages\n * their kind already describes; the owner adds posts and products after.\n */\n\n/** How many first posts a paid blog gets. */\nexport const AI_SITE_POSTS = 3\n\n/** How many first products a paid store gets: asked for, and the most written. */\nexport const AI_SITE_PRODUCTS = { min: 3, max: 6 } as const\n\n/** What a products row says when none of what we made could be written: our failure, refunded. */\nexport const AI_SITE_PRODUCTS_NOT_WRITTEN_COPY = 'Your first products could not be added this time.'\n\n/** What each part's row reads. */\nexport const AI_SITE_POSTS_LABEL = 'Writing your first posts'\nexport const AI_SITE_PRODUCTS_LABEL = 'Adding your first products'\n\n/** The blog the posts go in, and the addresses it may answer at (`model/ai-site-job.ts`). */\nexport { AI_SITE_BLOG_NAME, AI_SITE_BLOG_SLUGS } from '../model/ai-site-job'\n\n/** The input a site unit's own job carries what the part needs in. */\nexport const AI_SITE_CONTENT_INPUT = 'siteContent'\n\n/** Which part a site of these answers gets; `null` for the Free taste and every other kind. */\nexport function aiSiteContentPart(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n freeTaste: boolean,\n): 'posts' | 'products' | null {\n if (freeTaste) return null\n const kind = aiSiteKindOfInputs(inputs)?.id\n if (kind === 'blog') return 'posts'\n if (kind === 'store') return 'products'\n return null\n}\n\n/**\n * ASSUMED: what one post pass reads and writes beside its generation: the\n * host, the collection's slug claim and the entry's transaction.\n */\nexport const AI_SITE_POST_WRITES_MS = 1_500\n\n/** One post's pass: its generation and re-ask at the `copy.blog` ceiling, and its writes. */\nexport const AI_SITE_POST_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS[AI_BLOG_POST_STEP],\n maxTokens: AI_BLOG_POST_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_SITE_POST_WRITES_MS,\n})\n\n/** What a posts unit's job is told: the posts already written and the addresses the blog must not take. */\nexport interface AiSitePostsInput {\n written: Array<{ id: string; title: string }>\n total: number\n /** Addresses the site's own pages answer at, which the blog must not shadow. */\n avoidSlugs: string[]\n /** Who the posts are by: the business's name as given, else empty. */\n byline: string\n}\n\n/** What a products unit's job is told. */\nexport interface AiSiteProductsInput {\n min: number\n max: number\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction postsInputOf(job: Pick<AiJob, 'inputs'>): AiSitePostsInput {\n const raw = (job.inputs?.[AI_SITE_CONTENT_INPUT] ?? {}) as Partial<AiSitePostsInput>\n return {\n written: Array.isArray(raw.written) ? raw.written.filter((one) => one && typeof one.id === 'string') : [],\n total: typeof raw.total === 'number' && raw.total > 0 ? Math.floor(raw.total) : AI_SITE_POSTS,\n avoidSlugs: Array.isArray(raw.avoidSlugs) ? raw.avoidSlugs.map(str).filter(Boolean) : [],\n byline: str(raw.byline),\n }\n}\n\n/** The id of the `index`th post, of the blog its unit writes under `unitJobId`. */\nexport function aiSitePostId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** The id of the `index`th product a products unit writes. */\nexport function aiSiteProductId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** A site's subdomain and name, as its outputs and its posts' byline read them. */\nasync function readSite(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<{ subdomain: string | null; name: string } | null> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n if (!host.exists) return null\n const seo = (host.get('seo') ?? {}) as Record<string, unknown>\n const subdomain = str(host.get('subdomain')) || null\n return { subdomain, name: str(host.get('displayName')) || str(seo['title']) }\n}\n\nconst limitReview = (message: string) => ({ reason: 'limit' as const, message, findings: [] })\n\nexport interface AiSitePostsRunnerDeps {\n generate?: typeof generateAiBlogPost\n writeCollection?: typeof writeContentCollection\n writeEntry?: typeof writeContentEntryDraft\n /** Where each post's cover comes from; the stock library, else the starters, otherwise. */\n cover?: typeof aiSitePostCover\n}\n\n/** A post's cover's shape: the blog's cards' (`COLLECTION_LIST_COVER_RATIO`, the posts listing's). */\nconst AI_SITE_POST_COVER_ASPECT = 3 / 2\n\n/**\n * A post's cover (AGL-3676): a stock photo of the post's own title, copied\n * into the site's library, where the deployment has a stock library; else the\n * starter photo a post was always given, turned by the job so blogs differ.\n * The blog's cards and the post's page show it, for the owner to replace.\n * Never throws: a post whose search fails takes its starter.\n */\nexport async function aiSitePostCover(input: {\n job: Pick<AiJob, '$id' | 'hostId' | 'createdBy' | 'inputs' | 'brief'>\n title: string\n index: number\n total: number\n signal?: AbortSignal\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}): Promise<string | null> {\n const { job, index } = input\n if (job.hostId && input.title.trim()) {\n try {\n const source = (input.stockPhotos ?? aiLayoutStockPhotoSource)({\n hostId: job.hostId,\n uid: job.createdBy,\n seed: `${job.$id}:posts:${index}`,\n business: aiSiteWords(job.inputs).about || job.brief,\n sectionNames: ['Blog'],\n jobId: aiOriginJobId(job),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const [found] = source\n ? await source([\n {\n imageId: `cover${index}`,\n frameId: null,\n iconId: null,\n alt: input.title,\n aspect: AI_SITE_POST_COVER_ASPECT,\n sectionIndex: 0,\n role: 'gallery' as const,\n },\n ])\n : []\n if (found?.src) return found.src\n } catch (error) {\n console.warn('ai site posts: the stock photo failed; the starter covers it', { error: String(error) })\n }\n }\n const starters = aiLayoutStarterPhotos(\n Array.from({ length: Math.max(input.total, index + 1) }, (_, slot) => ({\n imageId: `cover${slot}`,\n frameId: null,\n iconId: null,\n alt: '',\n aspect: 16 / 9,\n sectionIndex: slot + 1,\n role: 'gallery' as const,\n })),\n job.$id,\n )\n return starters[index]?.src ?? null\n}\n\n/**\n * The posts unit's runner, on the job the scaffold derived for it: one post a\n * pass, continuing while posts remain. Its first pass makes the blog; every\n * pass finds it again under the unit's own id, and a post under its own id,\n * so a pass repeated after its write reports the post it already wrote.\n */\nexport function createAiSitePostsRunner(deps: AiSitePostsRunnerDeps = {}): AiJobStepRunner {\n const generate = deps.generate ?? generateAiBlogPost\n const writeCollection = deps.writeCollection ?? writeContentCollection\n const writeEntry = deps.writeEntry ?? writeContentEntryDraft\n const coverFor = deps.cover ?? aiSitePostCover\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, signal } = context\n const model = context.modelFor?.(AI_BLOG_POST_STEP) ?? aiModelForStep(AI_BLOG_POST_STEP)\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'Open the site before writing its posts.' }\n const input = postsInputOf(job)\n const index = input.written.length\n if (index >= input.total) return aiUnspentOutcome(model)\n const site = await readSite(firestore, job.hostId)\n if (!site) return { ...aiUnspentOutcome(model), failure: 'This site no longer exists.' }\n const avoid = new Set(input.avoidSlugs.map((slug) => slug.toLowerCase()))\n const blog = await writeCollection(firestore, {\n hostId: job.hostId,\n uid: job.createdBy,\n id: job.$id,\n displayName: AI_SITE_BLOG_NAME,\n slugs: AI_SITE_BLOG_SLUGS.filter((slug) => !avoid.has(slug)),\n now: context.now,\n })\n if (blog.ok === false) return aiUnspentOutcome(model, { review: limitReview(blog.error) })\n\n const generation = await generate({\n brief: job.brief,\n merchantWords: job.brief,\n earlierTitles: input.written.map((post) => post.title),\n index: index + 1,\n total: input.total,\n model,\n maxTokens: AI_SITE_POST_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const post = generation.value\n // A stock photo of its title as its cover, else a starter (AGL-3676).\n const cover = await coverFor({\n job,\n title: post.title,\n index,\n total: input.total,\n ...(signal ? { signal } : {}),\n }).catch(() => null)\n const byline = input.byline || site.name\n const written = await writeEntry(firestore, {\n hostId: job.hostId,\n collectionId: blog.id,\n uid: job.createdBy,\n id: aiSitePostId(job.$id, index),\n content: {\n title: post.title,\n excerpt: post.excerpt,\n body: post.body,\n seoDescription: post.seoDescription,\n ...(cover ? { coverImage: cover, coverImageAlt: post.title } : {}),\n ...(byline ? { authorName: byline } : {}),\n },\n now: context.now,\n })\n if (written.ok === false) return { ...spent, review: limitReview(written.error) }\n const output: AiJobOutput = {\n resource: 'entry',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: site.subdomain,\n label: written.title,\n note: `A post in ${blog.displayName}, at /${blog.slug}/${written.slug}. It goes live with the site.`,\n proposal: { collectionId: blog.id, collectionSlug: blog.slug, slug: written.slug },\n }\n return { ...spent, outputs: [output], ...(index + 1 < input.total ? { continue: true } : {}) }\n }\n}\n\nexport const runAiSitePostsUnit = createAiSitePostsRunner()\n\nexport interface AiSiteProductsRunnerDeps {\n /** The catalog's runner: the `products` step's. */\n catalog: AiJobStepRunner\n writerFor?: typeof pluginResourceDraftWriter\n /** Where each product's photo comes from; the stock library, else the starters, otherwise. */\n photos?: typeof aiSiteProductPhotos\n}\n\n/**\n * A photo for each product (AGL-3676), in order: a stock photo of the\n * product's own words, copied into the site's library, where the deployment\n * has a stock library; else a starter photo, as a page's empty picture takes\n * one. It fills the photo slot every product card and product page has, for\n * the owner to replace with their own; the product's note says so. Never\n * throws: a product with no photo is still written.\n */\nexport async function aiSiteProductPhotos(input: {\n job: Pick<AiJob, '$id' | 'hostId' | 'createdBy' | 'inputs' | 'brief'>\n names: readonly string[]\n /** What each product's photo should show, as its catalog proposed it, by position (AGL-3676). */\n shows?: readonly string[]\n signal?: AbortSignal\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}): Promise<Array<string | null>> {\n if (!input.names.length || !input.job.hostId) return input.names.map(() => null)\n const slots: AiLayoutPictureSlot[] = input.names.map((name, index) => ({\n imageId: `product${index}`,\n frameId: null,\n iconId: null,\n alt: name,\n // The product card's own shape (`cardStyle: 'photo'`).\n aspect: 4 / 5,\n sectionIndex: 0,\n role: 'gallery' as const,\n // Searched for by its own name, then what its photo shows, with the shop's\n // category, and held to that category (AGL-3676).\n product: { subjects: [name, input.shows?.[index] ?? ''].filter((words) => words.trim()) },\n }))\n const seed = `${input.job.$id}:products`\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n try {\n const source = (input.stockPhotos ?? aiLayoutStockPhotoSource)({\n hostId: input.job.hostId,\n uid: input.job.createdBy,\n seed,\n business: aiSiteWords(input.job.inputs).about || input.job.brief,\n sectionNames: ['Products'],\n jobId: aiOriginJobId(input.job),\n // Each product may try its name, its photo's subject, its category, a\n // gift's wrapper and the craft's broad searches (AGL-3660).\n searches: slots.length * 6,\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (source) found = await source(slots)\n } catch (error) {\n console.warn('ai site products: the stock photos failed; starter photos fill them', { error: String(error) })\n }\n const starters = aiLayoutStarterPhotos(slots, seed)\n const hostId = input.job.hostId\n // Every product has a photo (AGL-3676): a starter where no stock photo\n // named its category, never an empty tile.\n return slots.map(\n (_slot, index) =>\n aiSiteProductPhotoSrc(found[index]?.src, hostId) ?? aiSiteProductPhotoSrc(starters[index]?.src, hostId),\n )\n}\n\n/**\n * A photo as a product stores it: the form the console's picker writes when\n * an owner picks one from the library (`console-media-picker-provider`), the\n * asset's CDN path `/api/media/cdn/{scope}/{mediaId}`, or a starter's own\n * site path. A page's picture slot holds the library's `media:` reference\n * (`mediaNodeSrc`), which is what the stock source hands back, and the\n * product's rules take only an https address or a path on the site: the\n * candle store's guided start of 2026-10-09 (job FYNasg1h0S) wrote no\n * product at all because every photo was a `media:` reference. The path\n * names the asset, not its bytes, so a replace in the library still reaches\n * the product. Anything else is no photo, never a value the product refuses.\n */\nexport function aiSiteProductPhotoSrc(src: string | null | undefined, hostId: string): string | null {\n if (!src) return null\n const path = resolveMediaSrc(src, { hostId })\n if (!path || path.length > 2_000 || /[\\s\"'<>]/.test(path)) return null\n return path.startsWith('/') || path.startsWith('https://') ? path : null\n}\n\n/**\n * A proposed product's real choices (AGL-3676): each option with two or more\n * distinct values. An option of one value is no choice for a shopper to make\n * — the beta.237 Willow Wick gift set's \"Scent\" select offered nothing — so\n * it is left out, and its one value stays in the product's own words.\n */\nexport function aiSiteProductChoices(\n options: ReadonlyArray<{ name: string; values: readonly string[] }> | null | undefined,\n): Array<{ name: string; values: string[] }> {\n return (options ?? []).flatMap((option) => {\n const seen = new Set<string>()\n const values = option.values\n .map((value) => value.trim())\n .filter((value) => value && !seen.has(value.toLowerCase()) && !!seen.add(value.toLowerCase()))\n return option.name.trim() && values.length >= 2 ? [{ name: option.name.trim(), values }] : []\n })\n}\n\n/** The sentence the products unit's brief ends with, which the catalog's rules read as how many. */\nexport function aiSiteProductsBriefLine(input: AiSiteProductsInput = AI_SITE_PRODUCTS): string {\n return `Propose between ${input.min} and ${input.max} products: the store's first ones.`\n}\n\n/**\n * The products unit's runner: the `products` step's catalog for the store's\n * brief, then each proposed product, up to the most a site starts with,\n * written by the plugin that keeps products at its suggested price (else the\n * store's default), listed. A\n * product past the plan's allowance stops the writing there, and the row\n * says how many were added; none at all is the allowance's review. A photo\n * the product's rules refuse is dropped and the product written without it;\n * a product they refuse outright is skipped, and none written for that\n * reason is our failure, refunded, never the person's review (AGL-3676).\n */\nexport function createAiSiteProductsRunner(deps: AiSiteProductsRunnerDeps): AiJobStepRunner {\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n const keeper = writerFor('product')\n const model = context.modelFor?.('job.products') ?? aiModelForStep('job.products')\n if (!keeper || !job.hostId) return { ...aiUnspentOutcome(model), failure: 'Products are not available on this site.' }\n const outcome = await deps.catalog({\n ...context,\n job: { ...job, kind: 'products', inputs: { ...job.inputs, target: 'catalog' } },\n })\n if (outcome.refused || outcome.failure || outcome.review) return outcome\n const catalog = outcome.outputs.map((output) => ({ output, proposal: aiProductsProposalOf(output) })).find(\n (entry) => entry.proposal?.kind === 'catalog',\n )\n if (!catalog || catalog.proposal?.kind !== 'catalog') return { ...outcome, outputs: [] }\n const proposed = catalog.proposal.products.slice(0, AI_SITE_PRODUCTS.max)\n const photos = await (deps.photos ?? aiSiteProductPhotos)({\n job,\n names: proposed.map((product) => product.name),\n shows: proposed.map((product) => product.photo ?? ''),\n ...(context.signal ? { signal: context.signal } : {}),\n }).catch(() => proposed.map(() => null))\n const outputs: AiJobOutput[] = []\n let stopped: string | null = null\n /** The product's rules refusing what we made: ours, never the workspace's (AGL-3676). */\n let rejected: string | null = null\n for (const [index, product] of proposed.entries()) {\n const content: Record<string, unknown> = {\n name: product.name,\n type: product.type,\n // Listed at once, so no \"[gift card terms]\" gap reaches a shopper:\n // a sentence that leaves a fact for the owner is left out.\n description: aiSiteProductDescriptionWithoutGaps(product.description),\n tags: product.tags,\n // A choice is two values or more (AGL-3676): a \"Scent\" of one value\n // drew a select with nothing to pick on the live Willow Wick page.\n options: aiSiteProductChoices(product.options),\n seoTitle: product.seoTitle,\n seoDescription: product.seoDescription,\n comingSoon: true,\n // The catalog's suggested price for what the product is (Zach,\n // 2026-10-12), held to range once more here: none, or one out of\n // range, is left unstated and the writer gives the store's default.\n ...aiSiteProductPriceContent(product.price),\n }\n // A photo we picked that the product's rules refuse is dropped, never\n // the product: its slot stays empty for the owner's own.\n const photo = photos[index] ?? null\n const withPhoto = photo ? { ...content, mediaUrls: [photo] } : content\n const photoRefused =\n photo !== null &&\n keeper.writer.check(withPhoto, { hostId: job.hostId }).ok === false &&\n keeper.writer.check(content, { hostId: job.hostId }).ok\n if (photoRefused) {\n console.warn('ai site products: the product refused its photo; written without it', {\n orgId: job.orgId,\n jobId: job.$id,\n photo,\n })\n }\n const written = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: aiSiteProductId(job.$id, index),\n name: product.name,\n // Its suggested price, else the store's default, for the owner to\n // change (AGL-3676). Listed at once. Its photo slot holds a stock or\n // starter photo for the owner to replace.\n content: photoRefused ? content : withPhoto,\n })\n if (written.ok === false) {\n // A 400 is the product's rules refusing content we made: that one\n // product is skipped and the rest are still written. Anything else\n // (the allowance, the role, the site) stops the writing there.\n if (written.status === 400) {\n console.error('ai site products: the product writer refused a product we made', {\n orgId: job.orgId,\n jobId: job.$id,\n index,\n error: written.error,\n })\n rejected = written.error\n continue\n }\n stopped = written.error\n break\n }\n outputs.push({\n resource: 'product',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: catalog.output.hostSubdomain ?? null,\n label: written.name,\n note: AI_SITE_PRODUCT_NOTE,\n })\n }\n if (!outputs.length) {\n // Nothing written because the product's rules refused what we made:\n // the failure is ours, so the item's credits are refunded\n // (`aiUnitFailure` → `ours: true`). Only the workspace's allowance,\n // role or site is the person's to review.\n if (!stopped && rejected) return { ...outcome, outputs: [], failure: AI_SITE_PRODUCTS_NOT_WRITTEN_COPY }\n return { ...outcome, outputs: [], review: limitReview(stopped ?? 'No product could be added.') }\n }\n // The allowance that stopped the rest is said where the last one added is.\n const last = outputs[outputs.length - 1]\n if (stopped) {\n last.note = `${last.note} ${outputs.length} of ${proposed.length} added: ${stopped}.`\n } else if (outputs.length < proposed.length) {\n last.note = `${last.note} ${outputs.length} of ${proposed.length} added.`\n }\n return { ...outcome, outputs }\n }\n}\n\n/**\n * Whether a part may be built for this member on this site, asked before its\n * first pass so a refusal spends nothing; `null` admits. Products ask what a\n * `products` job asks (the plan's commerce feature, Commerce on and released\n * for the site) and the writer's own refusal (role, allowance); posts ask\n * that the member may write the site's content.\n */\nexport async function aiSiteContentRefusal(\n part: 'posts' | 'products',\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy' | 'inputs'> },\n deps: { writerFor?: typeof pluginResourceDraftWriter; admissionFor?: typeof aiJobAdmissionFor } = {},\n): Promise<string | null> {\n const { job, firestore } = context\n if (!job.hostId) return 'Open the site first.'\n if (part === 'posts') {\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n return hostRoleCanWrite((host.get('memberRoles') ?? {})[job.createdBy]) ? null : 'Editing requires the editor role'\n }\n const admission = (deps.admissionFor ?? aiJobAdmissionFor)('products')\n const refused: AiJobAdmissionRefusal | null = admission\n ? await admission({\n firestore,\n orgId: job.orgId,\n hostId: job.hostId,\n inputs: { target: 'catalog' },\n org: context.org ?? null,\n uid: job.createdBy,\n })\n : { status: 403, error: 'Products are not available on this site.' }\n if (refused) return refused.error\n const keeper = (deps.writerFor ?? pluginResourceDraftWriter)('product')\n if (!keeper) return 'Products are not available on this site.'\n const room = await keeper.writer.refusal({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n })\n return room?.error ?? null\n}\n\n/** The guided start's posts, published with its pages (AGL-3676). */\nexport interface AiSitePostsPublish {\n published: number\n kept: number\n /** The blog's own addresses, for the publish's cache drop. */\n paths: string[]\n}\n\n/**\n * Publishes the posts a guided start wrote, as the entry editor's Publish\n * does, once its pages are live. Never throws: a post that stays a draft is\n * the owner's to publish from Content.\n */\nexport async function aiPublishSitePosts(\n firestore: FirebaseFirestore.Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'createdBy'>; outputs: readonly AiJobOutput[]; now: Date },\n publish: typeof publishContentEntries = publishContentEntries,\n): Promise<AiSitePostsPublish | null> {\n const posts = input.outputs.filter((output) => output.resource === 'entry')\n const collectionId = str(posts[0]?.proposal?.['collectionId'])\n const collectionSlug = str(posts[0]?.proposal?.['collectionSlug'])\n if (!input.job.hostId || !posts.length || !collectionId) return null\n try {\n const result = await publish(firestore, {\n hostId: input.job.hostId,\n collectionId,\n uid: input.job.createdBy,\n ids: posts.map((post) => post.id),\n now: input.now,\n })\n if ('ok' in result) {\n console.warn('ai site posts not published', { jobId: input.job.$id, error: result.error })\n return { published: 0, kept: posts.length, paths: [] }\n }\n const slugs = posts\n .filter((post) => result.published.includes(post.id))\n .map((post) => str(post.proposal?.['slug']))\n .filter(Boolean)\n return {\n published: result.published.length,\n kept: result.kept.length,\n paths: collectionSlug ? [`/${collectionSlug}`, ...slugs.map((slug) => `/${collectionSlug}/${slug}`)] : [],\n }\n } catch (error) {\n console.error('ai site posts publish threw', { orgId: input.job.orgId, jobId: input.job.$id, error })\n return null\n }\n}\n\n/**\n * The site's listings a layout or a page unit is handed (AGL-3676,\n * `ai-layout-listings.ts`): the store's catalog once its products are written,\n * the blog once its posts are, each with the sections of the plan's pages\n * that list it. A layout is built before either, so it is told the store will\n * sell (`sells`, its ledger still owing the products part), which puts the\n * cart in its header.\n */\nexport function aiSiteListings(input: {\n outputs: readonly AiJobOutput[]\n screens: readonly AiLayoutListingScreen[]\n /** The ledger owes a store's first products: a layout built before them carries the cart. */\n sells?: boolean\n /**\n * The site is a store (`siteKind: 'store'`, AGL-3676): its Shop page and\n * its home list the catalog whatever its first products came to — the\n * live Hearth & Wick start's products step failed, and its Shop page was\n * six cards naming kinds of candle. An empty catalog says so in the grid.\n */\n store?: boolean\n /**\n * The site is a music site (`siteKind: 'music'`, AGL-3716): its Music page,\n * or its home, places an empty Music player for the artist's own tracks.\n * Nothing is sourced: the owner uploads the recordings.\n */\n music?: boolean\n}): AiLayoutListing[] {\n const listings: AiLayoutListing[] = []\n const products = input.outputs.filter((output) => output.resource === 'product' && !output.proposal)\n if (products.length || input.sells || input.store) {\n const contact = aiSiteContactPath(input.screens)\n listings.push({\n id: aiLayoutListingId('products'),\n kind: 'products',\n name: 'the shop',\n records: products.map((product) => product.label),\n ...(contact ? { emptyAction: { label: 'Get in touch', href: contact } } : {}),\n // A store whose first products were skipped or failed lists its\n // (empty) catalog, but its header carries no cart until it sells.\n ...(products.length || input.sells ? {} : { cart: false }),\n placements: aiLayoutListingPlacements('products', input.screens),\n })\n // A store that sells shows its customers' reviews and takes newsletter\n // sign-ups where its pages name them (AGL-3676): both commerce elements,\n // so only where the store's commerce runs, never on a store that cannot\n // sell yet.\n if (products.length || input.sells) {\n for (const kind of ['reviews', 'signup'] as const) {\n const placements = aiLayoutListingPlacements(kind, input.screens)\n if (placements.length) listings.push({ id: aiLayoutListingId(kind), kind, name: kind === 'reviews' ? 'the store' : 'the newsletter', records: [], placements })\n }\n }\n }\n const posts = input.outputs.filter((output) => output.resource === 'entry')\n const slug = str(posts[0]?.proposal?.['collectionSlug'])\n if (posts.length && slug) {\n listings.push({\n id: aiLayoutListingId('posts'),\n kind: 'posts',\n name: 'the blog',\n records: posts.map((post) => post.label),\n href: `/${slug}`,\n collectionSlug: slug,\n placements: aiLayoutListingPlacements('posts', input.screens),\n })\n }\n if (input.music) {\n const placements = aiLayoutListingPlacements('tracks', input.screens)\n if (placements.length) {\n listings.push({ id: aiLayoutListingId('tracks'), kind: 'tracks', name: 'the music', records: [], placements })\n }\n }\n return listings\n}\n\n/** The page a visitor gets in touch on, by its path: one whose address or name says contact, else the one placing a form. */\nfunction aiSiteContactPath(screens: readonly AiLayoutListingScreen[]): string | null {\n const path = (slug: string) => `/${slug.trim().replace(/^\\/+|\\/+$/g, '')}`\n const isPath = (slug: string) => /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/.test(path(slug))\n const named = screens.find((screen) => isPath(screen.slug) && /\\b(contact|get in touch|enquir|inquir)/i.test(`${screen.slug} ${screen.title}`))\n if (named) return path(named.slug)\n const form = screens.find((screen) => isPath(screen.slug) && screen.sections.some((section) => (section.uses ?? []).some((use) => /form/i.test(use))))\n return form ? path(form.slug) : null\n}\n\n/**\n * A proposed product's description with each sentence that leaves a gap for\n * the owner (\"Delivery details: [how and when the card is sent].\") taken\n * out (AGL-3676): a start's products are listed as soon as they are written,\n * and a live run's gift card showed its gaps on the product page. A line\n * break is kept; nothing else is changed.\n */\nexport function aiSiteProductDescriptionWithoutGaps(description: string): string {\n return description\n .split('\\n')\n .map((line) =>\n line\n .split(/(?<=[.!?])\\s+/)\n .filter((sentence) => !/\\[[^\\]]+\\]/.test(sentence))\n .join(' '),\n )\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** What each product a start wrote says on the job's page (AGL-3676). */\nexport const AI_SITE_PRODUCT_NOTE =\n 'On your store at a suggested price. Change its price — and add your own photo — in Products.'\n\n/** The note a store's products row carries once they are written (AGL-3676): the step left before it sells. */\nexport const AI_SITE_PRODUCTS_PRICE_NOTE =\n 'Your products are on your store at suggested prices. Review and change them in Products.'\n\n/**\n * The price a start writes a product with (Zach, 2026-10-12): the catalog's\n * suggestion as `aiCatalogPrice` keeps it, stated as the writer reads one\n * (`priceUsd`); nothing when there is none in range, so the writer gives the\n * store's default price. Never `priceUsd: null`, which would leave the\n * product unpriced on purpose.\n */\nexport function aiSiteProductPriceContent(price: unknown): { priceUsd?: number } {\n const kept = aiCatalogPrice(price)\n return kept === null ? {} : { priceUsd: kept }\n}\n\n/** What a page of a blog or a store is told about the posts or products built before it. */\nexport function aiSiteContentBriefLines(outputs: readonly AiJobOutput[]): string[] {\n const posts = outputs.filter((output) => output.resource === 'entry')\n const products = outputs.filter((output) => output.resource === 'product' && !output.proposal)\n const lines: string[] = []\n if (posts.length) {\n const slug = str(posts[0].proposal?.['collectionSlug'])\n lines.push(\n `This site's blog${slug ? ` at /${slug}` : ''} has these posts: ${posts.map((post) => `“${post.label}”`).join(', ')}.`,\n 'Where a page features writing, feature these posts by their titles; never name a post that is not in this list.',\n )\n }\n if (products.length) {\n lines.push(\n `This store's products are: ${products.map((product) => `“${product.label}”`).join(', ')}.`,\n 'Where a page features products, feature these by their names; never name another product, and never state a price.',\n // The collection tiles a storefront home shows (AGL-3676): its own range, grouped.\n 'Where a page shows the shop by collection, group these products into two to four collections by what they are (a kind, a use or an occasion), one item each, named for the group and never for one product.',\n )\n }\n return lines\n}\n"],"names":["resolveMediaSrc","hostRoleCanWrite","pluginResourceDraftWriter","publishContentEntries","writeContentCollection","writeContentEntryDraft","aiCatalogPrice","aiProductsProposalOf","aiSiteKindOfInputs","AI_SITE_BLOG_NAME","AI_SITE_BLOG_SLUGS","aiSiteWords","AI_STEP_TIERS","aiModelForStep","AI_BLOG_POST_MAX_TOKENS","AI_BLOG_POST_STEP","generateAiBlogPost","aiLayoutStarterPhotos","aiOriginJobId","aiLayoutStockPhotoSource","aiLayoutListingId","aiLayoutListingPlacements","aiJobAdmissionFor","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_SITE_POSTS","AI_SITE_PRODUCTS","min","max","AI_SITE_PRODUCTS_NOT_WRITTEN_COPY","AI_SITE_POSTS_LABEL","AI_SITE_PRODUCTS_LABEL","AI_SITE_CONTENT_INPUT","aiSiteContentPart","inputs","freeTaste","kind","id","AI_SITE_POST_WRITES_MS","AI_SITE_POST_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","trim","postsInputOf","job","raw","written","Array","isArray","filter","one","total","Math","floor","avoidSlugs","map","Boolean","byline","aiSitePostId","unitJobId","index","aiSiteProductId","readSite","firestore","hostId","host","collection","doc","get","exists","seo","subdomain","name","limitReview","message","reason","findings","AI_SITE_POST_COVER_ASPECT","aiSitePostCover","input","starters","title","source","stockPhotos","uid","createdBy","seed","$id","business","about","brief","sectionNames","jobId","signal","found","imageId","frameId","iconId","alt","aspect","sectionIndex","role","src","error","console","warn","String","from","length","_","slot","createAiSitePostsRunner","deps","generate","writeCollection","writeEntry","coverFor","cover","context","model","modelFor","failure","site","avoid","Set","slug","toLowerCase","blog","displayName","slugs","has","now","ok","review","generation","merchantWords","earlierTitles","post","spent","status","refused","catch","collectionId","content","excerpt","body","seoDescription","coverImage","coverImageAlt","authorName","output","resource","hostSubdomain","label","note","proposal","collectionSlug","outputs","continue","runAiSitePostsUnit","aiSiteProductPhotos","names","slots","product","subjects","shows","words","searches","_slot","aiSiteProductPhotoSrc","path","test","startsWith","aiSiteProductChoices","options","flatMap","option","seen","values","add","aiSiteProductsBriefLine","createAiSiteProductsRunner","writerFor","catalog","keeper","outcome","target","find","entry","proposed","products","slice","photos","photo","stopped","rejected","entries","type","description","aiSiteProductDescriptionWithoutGaps","tags","seoTitle","comingSoon","aiSiteProductPriceContent","price","withPhoto","mediaUrls","photoRefused","writer","check","orgId","write","org","push","AI_SITE_PRODUCT_NOTE","last","aiSiteContentRefusal","part","admission","admissionFor","room","refusal","aiPublishSitePosts","publish","posts","result","ids","published","kept","paths","includes","aiSiteListings","listings","sells","store","contact","aiSiteContactPath","screens","records","emptyAction","href","cart","placements","music","replace","isPath","named","screen","form","sections","some","section","uses","use","split","line","sentence","join","AI_SITE_PRODUCTS_PRICE_NOTE","priceUsd","aiSiteContentBriefLines","lines"],"mappings":"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"}
@@ -237,6 +237,8 @@ export declare function aiDatasetListingsOf(datasets: ReadonlyArray<{
237
237
  records?: string[];
238
238
  fields?: AiLayoutListingField[];
239
239
  imageField?: string | null;
240
+ /** The record's page-address field, where the writer added one (AGL-3727). */
241
+ addressField?: string | null;
240
242
  }>, screens: readonly AiLayoutListingScreen[]): AiLayoutListing[];
241
243
  /**
242
244
  * The site's datasets a layout or a page unit is handed (AGL-3616), each a