@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.288

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 (228) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  3. package/src/lib/assets/site-styles/professional.webp +0 -0
  4. package/src/lib/assets/site-styles/store.webp +0 -0
  5. package/src/lib/assets/site-styles/trades.webp +0 -0
  6. package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
  7. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  8. package/src/lib/declarations.js +5 -2
  9. package/src/lib/declarations.js.map +1 -1
  10. package/src/lib/declarations.server.js +12 -0
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
  13. package/src/lib/jobs/ai-build-capabilities.js +27 -2
  14. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  15. package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
  16. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  17. package/src/lib/jobs/ai-image-reads.d.ts +59 -0
  18. package/src/lib/jobs/ai-image-reads.js +138 -0
  19. package/src/lib/jobs/ai-image-reads.js.map +1 -0
  20. package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
  21. package/src/lib/jobs/ai-job-build-step.js +83 -12
  22. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  23. package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
  24. package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
  25. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
  27. package/src/lib/jobs/ai-job-import-step.js +15 -11
  28. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
  30. package/src/lib/jobs/ai-job-insight-step.js +23 -5
  31. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  32. package/src/lib/jobs/ai-job-layout-language.js +2 -1
  33. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  34. package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
  35. package/src/lib/jobs/ai-job-media-step.js +6 -8
  36. package/src/lib/jobs/ai-job-media-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
  38. package/src/lib/jobs/ai-job-navigation-step.js +7 -9
  39. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-page-language.js +7 -1
  41. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-sections.js +16 -3
  43. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  44. package/src/lib/jobs/ai-job-page-step.js +9 -1
  45. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  46. package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
  47. package/src/lib/jobs/ai-job-plan-step.js +78 -13
  48. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
  50. package/src/lib/jobs/ai-job-privacy-step.js +14 -15
  51. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-products-step.js +5 -3
  53. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
  55. package/src/lib/jobs/ai-job-settings-step.js +7 -8
  56. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
  58. package/src/lib/jobs/ai-job-site-content.js +67 -8
  59. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  60. package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
  61. package/src/lib/jobs/ai-job-site-datasets.js +300 -30
  62. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-step.js +6 -3
  64. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
  66. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
  67. package/src/lib/jobs/ai-job-text-step.js +2 -1
  68. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  69. package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
  70. package/src/lib/jobs/ai-job-tracking-step.js +7 -8
  71. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  72. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
  73. package/src/lib/jobs/ai-jobs-beat.js +18 -8
  74. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  75. package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
  76. package/src/lib/jobs/ai-jobs-notify.js +136 -3
  77. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  78. package/src/lib/jobs/ai-layout-stock-photos.d.ts +155 -7
  79. package/src/lib/jobs/ai-layout-stock-photos.js +1517 -220
  80. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  81. package/src/lib/jobs/ai-product-image.d.ts +14 -0
  82. package/src/lib/jobs/ai-product-image.js +73 -21
  83. package/src/lib/jobs/ai-product-image.js.map +1 -1
  84. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
  85. package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
  86. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  87. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
  88. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
  89. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
  90. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
  91. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
  92. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
  93. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
  94. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
  95. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
  96. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
  97. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
  98. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
  99. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
  100. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
  101. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
  102. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
  103. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
  104. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
  105. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
  106. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
  107. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
  108. package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
  109. package/src/lib/layout-language/ai-layout-compiler.js +194 -18
  110. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  111. package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
  112. package/src/lib/layout-language/ai-layout-copy.js +383 -3
  113. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  114. package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
  115. package/src/lib/layout-language/ai-layout-design.js +31 -11
  116. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  117. package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
  118. package/src/lib/layout-language/ai-layout-listings.js +9 -1
  119. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  120. package/src/lib/layout-language/ai-layout-pictures.d.ts +39 -1
  121. package/src/lib/layout-language/ai-layout-pictures.js +141 -4
  122. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  123. package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
  124. package/src/lib/layout-language/ai-layout-storefront.js +100 -20
  125. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
  126. package/src/lib/model/ai-build-job.d.ts +7 -0
  127. package/src/lib/model/ai-build-job.js +36 -7
  128. package/src/lib/model/ai-build-job.js.map +1 -1
  129. package/src/lib/model/ai-build-plan.d.ts +28 -4
  130. package/src/lib/model/ai-build-plan.js +49 -9
  131. package/src/lib/model/ai-build-plan.js.map +1 -1
  132. package/src/lib/model/ai-build-progress.js +3 -1
  133. package/src/lib/model/ai-build-progress.js.map +1 -1
  134. package/src/lib/model/ai-job-notice.d.ts +8 -0
  135. package/src/lib/model/ai-job-notice.js +33 -0
  136. package/src/lib/model/ai-job-notice.js.map +1 -1
  137. package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
  138. package/src/lib/model/ai-job-plan-lines.js +6 -1
  139. package/src/lib/model/ai-job-plan-lines.js.map +1 -1
  140. package/src/lib/model/ai-jobs.types.d.ts +8 -0
  141. package/src/lib/model/ai-jobs.types.js.map +1 -1
  142. package/src/lib/model/ai-permissions.d.ts +13 -0
  143. package/src/lib/model/ai-permissions.js +29 -1
  144. package/src/lib/model/ai-permissions.js.map +1 -1
  145. package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
  146. package/src/lib/model/ai-plan-said-sections.js +22 -5
  147. package/src/lib/model/ai-plan-said-sections.js.map +1 -1
  148. package/src/lib/model/ai-products.d.ts +18 -0
  149. package/src/lib/model/ai-products.js +32 -0
  150. package/src/lib/model/ai-products.js.map +1 -1
  151. package/src/lib/model/ai-site-context.d.ts +6 -2
  152. package/src/lib/model/ai-site-context.js +8 -2
  153. package/src/lib/model/ai-site-context.js.map +1 -1
  154. package/src/lib/model/ai-site-inspiration.d.ts +3 -2
  155. package/src/lib/model/ai-site-inspiration.js +3 -2
  156. package/src/lib/model/ai-site-inspiration.js.map +1 -1
  157. package/src/lib/plugin.js +4 -13
  158. package/src/lib/plugin.js.map +1 -1
  159. package/src/lib/providers/anthropic.js +9 -2
  160. package/src/lib/providers/anthropic.js.map +1 -1
  161. package/src/lib/providers/catalog.js +14 -5
  162. package/src/lib/providers/catalog.js.map +1 -1
  163. package/src/lib/providers/contract.d.ts +24 -1
  164. package/src/lib/providers/contract.js.map +1 -1
  165. package/src/lib/providers/routing.js +5 -2
  166. package/src/lib/providers/routing.js.map +1 -1
  167. package/src/lib/runtime/ai-dataset-generation.d.ts +120 -1
  168. package/src/lib/runtime/ai-dataset-generation.js +352 -3
  169. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  170. package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
  171. package/src/lib/runtime/ai-dev-replay.js +4 -2
  172. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  173. package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
  174. package/src/lib/runtime/ai-doctrine-validators.js +345 -28
  175. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  176. package/src/lib/runtime/ai-doctrine.d.ts +5 -4
  177. package/src/lib/runtime/ai-doctrine.js +10 -3
  178. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  179. package/src/lib/runtime/ai-icon-library.d.ts +27 -1
  180. package/src/lib/runtime/ai-icon-library.js +30 -3
  181. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  182. package/src/lib/runtime/ai-products-generation.d.ts +11 -1
  183. package/src/lib/runtime/ai-products-generation.js +51 -4
  184. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  185. package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
  186. package/src/lib/runtime/assist-answer-cache.js +5 -2
  187. package/src/lib/runtime/assist-answer-cache.js.map +1 -1
  188. package/src/lib/server/ai-allotments.js +7 -2
  189. package/src/lib/server/ai-allotments.js.map +1 -1
  190. package/src/lib/server/ai-experiment-versions.js +5 -5
  191. package/src/lib/server/ai-experiment-versions.js.map +1 -1
  192. package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
  193. package/src/lib/server/ai-host-permissions-roster.js +33 -0
  194. package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
  195. package/src/lib/server/ai-host-permissions.js +8 -6
  196. package/src/lib/server/ai-host-permissions.js.map +1 -1
  197. package/src/lib/server/ai-image-reads-route.d.ts +17 -0
  198. package/src/lib/server/ai-image-reads-route.js +106 -0
  199. package/src/lib/server/ai-image-reads-route.js.map +1 -0
  200. package/src/lib/server/ai-seo-apply.js +5 -5
  201. package/src/lib/server/ai-seo-apply.js.map +1 -1
  202. package/src/lib/server/ai-site-import-route.js +7 -4
  203. package/src/lib/server/ai-site-import-route.js.map +1 -1
  204. package/src/lib/server/assist-build.d.ts +15 -1
  205. package/src/lib/server/assist-build.js +30 -3
  206. package/src/lib/server/assist-build.js.map +1 -1
  207. package/src/lib/server/assist-chat.d.ts +11 -7
  208. package/src/lib/server/assist-chat.js +185 -48
  209. package/src/lib/server/assist-chat.js.map +1 -1
  210. package/src/lib/server/assist-docs-query.d.ts +51 -0
  211. package/src/lib/server/assist-docs-query.js +57 -0
  212. package/src/lib/server/assist-docs-query.js.map +1 -0
  213. package/src/lib/server/assist-jobs-context.d.ts +102 -0
  214. package/src/lib/server/assist-jobs-context.js +227 -0
  215. package/src/lib/server/assist-jobs-context.js.map +1 -0
  216. package/src/lib/server/assist-view-context.d.ts +8 -4
  217. package/src/lib/server/assist-view-context.js +12 -7
  218. package/src/lib/server/assist-view-context.js.map +1 -1
  219. package/src/lib/server.js +12 -0
  220. package/src/lib/server.js.map +1 -1
  221. package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
  222. package/src/lib/tools/ai-dataset-tool.js +24 -7
  223. package/src/lib/tools/ai-dataset-tool.js.map +1 -1
  224. package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
  225. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  226. package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
  227. package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
  228. package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
@@ -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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\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, aiCatalogEntry, aiDefaultModelFor } 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, aiStockPhotoWordsOf } 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. The scaffold now\n * builds one more part for those two kinds, before the pages, each its own\n * row with its own timer and credits — a store's on a paid plan only:\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 on a paid plan, `AI_SITE_FREE_POSTS` on the\n * Free taste, 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 * ── On the Free taste ────────────────────────────────────────────────────\n *\n * A guided start's Free blog writes ONE first post (Zach, 2026-10-14,\n * AGL-3798), and a Free store no products. Built on an empty site with its\n * layout and form, a Free site leaves 57 credits once its eight sections and\n * its retried page's room are held (`aiFreeSiteSectionsWithin`, AGL-3660).\n * One post at its worst is 4 on the fast tier since AGL-3781 (Claude Haiku\n * 5.5 at the platform's markup), so a Free blog's post is written there\n * (`aiSitePostModel`): on the balanced tier `copy.blog` routes to, the same\n * post is 68, past what the wall leaves. The wall counts it\n * (`AI_FREE_SITE_WORST_CASE_CREDITS.post`), and its eight sections still fit\n * beside it. A Free plan includes neither the `commerce` feature nor a\n * product (`productsPerHost` is 0), so a Free store keeps the pages its kind\n * describes and the owner adds products after.\n * `ai-job-free-site.spec.ts` shows the arithmetic.\n */\n\n/** How many first posts a paid blog gets. */\nexport const AI_SITE_POSTS = 3\n\n/** How many first posts a Free blog gets (`model/ai-site-job.ts`, which the Free wall reads). */\nexport { AI_SITE_FREE_POSTS } from '../model/ai-site-job'\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; a Free blog's one post reads in the singular. */\nexport const AI_SITE_POSTS_LABEL = 'Writing your first posts'\nexport const AI_SITE_POST_LABEL = 'Writing your first post'\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: a blog's posts, a paid store's products, else `null`. */\nexport function aiSiteContentPart(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n freeTaste: boolean,\n): 'posts' | 'products' | null {\n const kind = aiSiteKindOfInputs(inputs)?.id\n if (kind === 'blog') return 'posts'\n if (kind === 'store' && !freeTaste) return 'products'\n return null\n}\n\n/**\n * The model a site's post is written on: the routed one, except that a Free\n * blog's post is written on its provider's fast tier, as a Free site's plan\n * and look are (`aiSitePlanModel`, `aiSiteLookModel`). On the balanced tier\n * `copy.blog` routes to, one post at its worst is past what the Free wall\n * leaves. A model the creator picked by name stays theirs.\n */\nexport function aiSitePostModel(job: Pick<AiJob, 'model'>, routed: string, freeTaste: boolean): string {\n if (!freeTaste) return routed\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return routed\n const entry = aiCatalogEntry(routed)\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? routed\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' | 'plan'>\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 ...aiStockPhotoWordsOf(job),\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 freeTaste = Boolean(context.org) && resolveEffectivePlan(context.org as never) === 'free'\n const routed = context.modelFor?.(AI_BLOG_POST_STEP) ?? aiModelForStep(AI_BLOG_POST_STEP)\n const model = aiSitePostModel(job, routed, freeTaste)\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' | 'plan'>\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 ...aiStockPhotoWordsOf(input.job),\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","resolveEffectivePlan","pluginResourceDraftWriter","publishContentEntries","writeContentCollection","writeContentEntryDraft","aiCatalogPrice","aiProductsProposalOf","aiSiteKindOfInputs","AI_SITE_BLOG_NAME","AI_SITE_BLOG_SLUGS","aiSiteWords","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiModelForStep","AI_BLOG_POST_MAX_TOKENS","AI_BLOG_POST_STEP","generateAiBlogPost","aiLayoutStarterPhotos","aiOriginJobId","aiLayoutStockPhotoSource","aiStockPhotoWordsOf","aiLayoutListingId","aiLayoutListingPlacements","aiJobAdmissionFor","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_SITE_POSTS","AI_SITE_FREE_POSTS","AI_SITE_PRODUCTS","min","max","AI_SITE_PRODUCTS_NOT_WRITTEN_COPY","AI_SITE_POSTS_LABEL","AI_SITE_POST_LABEL","AI_SITE_PRODUCTS_LABEL","AI_SITE_CONTENT_INPUT","aiSiteContentPart","inputs","freeTaste","kind","id","aiSitePostModel","job","routed","entry","picked","model","trim","provider","undefined","AI_SITE_POST_WRITES_MS","AI_SITE_POST_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","postsInputOf","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","org","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","proposed","products","slice","photos","photo","stopped","rejected","entries","type","description","aiSiteProductDescriptionWithoutGaps","tags","seoTitle","comingSoon","aiSiteProductPriceContent","price","withPhoto","mediaUrls","photoRefused","writer","check","orgId","write","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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"}
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 { holdsSiteKey, siteKeyRefusal } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\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, aiCatalogEntry, aiDefaultModelFor } 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, aiStockPhotoWordsOf } 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. The scaffold now\n * builds one more part for those two kinds, before the pages, each its own\n * row with its own timer and credits — a store's on a paid plan only:\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 on a paid plan, `AI_SITE_FREE_POSTS` on the\n * Free taste, 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 * ── On the Free taste ────────────────────────────────────────────────────\n *\n * A guided start's Free blog writes ONE first post (Zach, 2026-10-14,\n * AGL-3798), and a Free store no products. Built on an empty site with its\n * layout and form, a Free site leaves 57 credits once its eight sections and\n * its retried page's room are held (`aiFreeSiteSectionsWithin`, AGL-3660).\n * One post at its worst is 4 on the fast tier since AGL-3781 (Claude Haiku\n * 5.5 at the platform's markup), so a Free blog's post is written there\n * (`aiSitePostModel`): on the balanced tier `copy.blog` routes to, the same\n * post is 68, past what the wall leaves. The wall counts it\n * (`AI_FREE_SITE_WORST_CASE_CREDITS.post`), and its eight sections still fit\n * beside it. A Free plan includes neither the `commerce` feature nor a\n * product (`productsPerHost` is 0), so a Free store keeps the pages its kind\n * describes and the owner adds products after.\n * `ai-job-free-site.spec.ts` shows the arithmetic.\n */\n\n/** How many first posts a paid blog gets. */\nexport const AI_SITE_POSTS = 3\n\n/** How many first posts a Free blog gets (`model/ai-site-job.ts`, which the Free wall reads). */\nexport { AI_SITE_FREE_POSTS } from '../model/ai-site-job'\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; a Free blog's one post reads in the singular. */\nexport const AI_SITE_POSTS_LABEL = 'Writing your first posts'\nexport const AI_SITE_POST_LABEL = 'Writing your first post'\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: a blog's posts, a paid store's products, else `null`. */\nexport function aiSiteContentPart(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n freeTaste: boolean,\n): 'posts' | 'products' | null {\n const kind = aiSiteKindOfInputs(inputs)?.id\n if (kind === 'blog') return 'posts'\n if (kind === 'store' && !freeTaste) return 'products'\n return null\n}\n\n/**\n * The model a site's post is written on: the routed one, except that a Free\n * blog's post is written on its provider's fast tier, as a Free site's plan\n * and look are (`aiSitePlanModel`, `aiSiteLookModel`). On the balanced tier\n * `copy.blog` routes to, one post at its worst is past what the Free wall\n * leaves. A model the creator picked by name stays theirs.\n */\nexport function aiSitePostModel(job: Pick<AiJob, 'model'>, routed: string, freeTaste: boolean): string {\n if (!freeTaste) return routed\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return routed\n const entry = aiCatalogEntry(routed)\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? routed\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' | 'plan'>\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 ...aiStockPhotoWordsOf(job),\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 freeTaste = Boolean(context.org) && resolveEffectivePlan(context.org as never) === 'free'\n const routed = context.modelFor?.(AI_BLOG_POST_STEP) ?? aiModelForStep(AI_BLOG_POST_STEP)\n const model = aiSitePostModel(job, routed, freeTaste)\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' | 'plan'>\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 ...aiStockPhotoWordsOf(input.job),\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, samples: readonly string[] | null = null): string {\n const count = `Propose between ${input.min} and ${input.max} products: the store's first ones.`\n // A store whose brief names only kinds of product starts with samples of them (AGL-4065).\n return samples?.length\n ? `${count} The brief names kinds of product and no product, so each is a sample of one of these kinds, which the owner replaces with their own: ${samples.join('; ')}. Name each as a plain item of its kind, never a brand, a model, or the kind with \"Item\", \"Order\" or \"Part\" after it.`\n : count\n}\n\n// ── Sample products (AGL-4065) ──────────────────────────────────────────\n\n/**\n * THE Q1 DECISION (AGL-4065, 2026-10-28): a store whose brief names kinds\n * of product and no product starts with SAMPLE products, one or more of\n * each kind, at a suggested price as before. Each is named as a plain item\n * of its kind (\"Knobby dirt bike tire\", never \"Riding Gear Item\"), written\n * with the commerce plugin's `sample` mark, which Products shows as a sample\n * to replace and the storefront never does, and the job's note says they\n * were made from the kinds the brief named. Products the brief does name\n * keep the rule they always had.\n */\nexport const AI_SITE_SAMPLE_PRODUCTS_INPUT = 'siteSampleProducts'\n\n/**\n * What a sample product's name carries after it, on the storefront too:\n * nothing, so a shopper reads a plain product; Products marks it instead.\n */\nexport const AI_SITE_SAMPLE_PRODUCT_NAME_SUFFIX = ''\n\n/** What each sample product a start wrote says on the job's page (AGL-4065). */\nexport const AI_SITE_SAMPLE_PRODUCT_NOTE =\n 'A sample made from the kinds of product you named, at a suggested price. Replace it with your own product in Products.'\n\n/** The note a store's products row carries when they are samples (AGL-4065). */\nexport const AI_SITE_SAMPLE_PRODUCTS_NOTE =\n 'Your store starts with sample products made from the kinds of product you named, at suggested prices. Replace each with your own in Products.'\n\n/** Where a business type lists what it sells: after a colon, or after \"selling\", \"offering\"… */\nconst SELLS_LIST = /(?::\\s*|\\b(?:selling|sells|sell|offering|offers|stocking|stocks|carrying|carries|making|makes)\\s+)(.+)$/i\n\n/** A listed thing that is what the shop does rather than something it sells. */\nconst SERVICE_HEAD =\n /\\b(?:orders?|pick-?ups?|deliver(?:y|ies)|shipping|repairs?|services?|installations?|installs?|rentals?|advice|consultations?|fittings?|fitment|bookings?|quotes?|estimates?|classes|lessons?|workshops?|tours?|events?)$/i\n\n/** A listed thing's last word that names a kind and never one product. */\nconst KIND_WORD =\n /\\b(?:parts?|gear|items?|accessor(?:y|ies)|suppl(?:y|ies)|equipment|products?|goods|merchandise|essentials?|stuff|things?)$/i\n\n/** What a listed thing is said with that is not what it is: \"online\", \"to order\", \"and more\". */\nconst LISTED_TAIL = /\\s+(?:online|in[- ]store|to order|by hand|and more|etc\\.?|for (?:sale|you)|near you)$/i\n\n/**\n * The kinds of product a store's brief lists and no product of its own\n * (AGL-4065), which its first products are samples of; `null` where the\n * brief names its products or lists nothing it sells. Read off the business\n * type the person gave: what follows a colon, or \"selling\", is a list of\n * what the shop sells, less what it does (\"special orders\", \"in-store\n * pickup\"). One thing said in two words or more of what it is (\"hand-poured\n * soy candles\") names the products; anything else is kinds (\"OEM and\n * aftermarket parts, tires, riding gear\"). Pure.\n */\nexport function aiSiteSampleProductKinds(inputs: Readonly<Record<string, unknown>> | null | undefined): string[] | null {\n const business = str(inputs?.['businessType']).replace(/\\s+/g, ' ')\n const list = SELLS_LIST.exec(business)?.[1]\n if (!list) return null\n const kinds = list\n .split(/[;,]|\\s+(?:&|\\+)\\s+/)\n .map((item) => item.trim().replace(/^(?:and|or)\\s+/i, '').replace(/[.!]+$/, ''))\n .map((item) => {\n let kept = item\n while (LISTED_TAIL.test(kept)) kept = kept.replace(LISTED_TAIL, '')\n return kept.trim()\n })\n .filter((item) => item && !SERVICE_HEAD.test(item))\n if (!kinds.length) return null\n const [only] = kinds\n if (kinds.length === 1 && only && only.split(/[\\s-]+/).length >= 2 && !KIND_WORD.test(only)) return null\n return kinds.slice(0, AI_SITE_PRODUCTS.max)\n}\n\n/** Whether a store's brief names its products (AGL-4065): it lists no kinds to make samples of. Pure. */\nexport function aiSiteBriefNamesProducts(inputs: Readonly<Record<string, unknown>> | null | undefined): boolean {\n return aiSiteSampleProductKinds(inputs) === null\n}\n\n/** The kinds a products unit's job makes samples of, as the site start handed them; `null` for none. */\nfunction sampleKindsOf(job: Pick<AiJob, 'inputs'>): string[] | null {\n const raw = job.inputs?.[AI_SITE_SAMPLE_PRODUCTS_INPUT]\n const kinds = Array.isArray(raw) ? raw.map(str).filter(Boolean) : []\n return kinds.length ? kinds : null\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 // Samples of the kinds the brief named, for the owner to replace (AGL-4065).\n const samples = sampleKindsOf(job) !== null\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 // A sample the owner replaces (AGL-4065): Products says so, the storefront never.\n ...(samples ? { sample: 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: samples ? `${product.name}${AI_SITE_SAMPLE_PRODUCT_NAME_SUFFIX}` : 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: samples ? AI_SITE_SAMPLE_PRODUCT_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 holdsSiteKey(host, job.createdBy, 'site.edit') ? null : siteKeyRefusal('site.edit')\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","holdsSiteKey","siteKeyRefusal","resolveEffectivePlan","pluginResourceDraftWriter","publishContentEntries","writeContentCollection","writeContentEntryDraft","aiCatalogPrice","aiProductsProposalOf","aiSiteKindOfInputs","AI_SITE_BLOG_NAME","AI_SITE_BLOG_SLUGS","aiSiteWords","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiModelForStep","AI_BLOG_POST_MAX_TOKENS","AI_BLOG_POST_STEP","generateAiBlogPost","aiLayoutStarterPhotos","aiOriginJobId","aiLayoutStockPhotoSource","aiStockPhotoWordsOf","aiLayoutListingId","aiLayoutListingPlacements","aiJobAdmissionFor","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_SITE_POSTS","AI_SITE_FREE_POSTS","AI_SITE_PRODUCTS","min","max","AI_SITE_PRODUCTS_NOT_WRITTEN_COPY","AI_SITE_POSTS_LABEL","AI_SITE_POST_LABEL","AI_SITE_PRODUCTS_LABEL","AI_SITE_CONTENT_INPUT","aiSiteContentPart","inputs","freeTaste","kind","id","aiSitePostModel","job","routed","entry","picked","model","trim","provider","undefined","AI_SITE_POST_WRITES_MS","AI_SITE_POST_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","postsInputOf","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","org","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","samples","count","join","AI_SITE_SAMPLE_PRODUCTS_INPUT","AI_SITE_SAMPLE_PRODUCT_NAME_SUFFIX","AI_SITE_SAMPLE_PRODUCT_NOTE","AI_SITE_SAMPLE_PRODUCTS_NOTE","SELLS_LIST","SERVICE_HEAD","KIND_WORD","LISTED_TAIL","aiSiteSampleProductKinds","replace","list","exec","kinds","split","item","kept","only","slice","aiSiteBriefNamesProducts","sampleKindsOf","createAiSiteProductsRunner","writerFor","catalog","keeper","outcome","target","find","proposed","products","photos","photo","stopped","rejected","entries","type","description","aiSiteProductDescriptionWithoutGaps","tags","seoTitle","comingSoon","aiSiteProductPriceContent","price","sample","withPhoto","mediaUrls","photoRefused","writer","check","orgId","write","push","AI_SITE_PRODUCT_NOTE","last","aiSiteContentRefusal","part","admission","admissionFor","room","refusal","aiPublishSitePosts","publish","posts","result","ids","published","paths","includes","aiSiteListings","listings","sells","store","contact","aiSiteContactPath","screens","records","emptyAction","href","cart","placements","music","isPath","named","screen","form","sections","some","section","uses","use","line","sentence","AI_SITE_PRODUCTS_PRICE_NOTE","priceUsd","aiSiteContentBriefLines","lines"],"mappings":"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"}
@@ -19,7 +19,7 @@ import { type AiBuildPlan, type AiBuildPlanCreate, type AiBuildPlanScreen } from
19
19
  import type { AiInventoryDataset, AiSiteInventory } from '../model/ai-site-inventory';
20
20
  import type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types';
21
21
  import type { AiPlanCapabilities } from '../model/ai-plan-capabilities';
22
- import { generateAiDataset } from '../runtime/ai-dataset-generation';
22
+ import { generateAiDataset, type AiDatasetSplit } from '../runtime/ai-dataset-generation';
23
23
  import { type AiLayoutListing, type AiLayoutListingField, type AiLayoutListingScreen } from '../layout-language/ai-layout-listings';
24
24
  import { type AiLayoutPicturePhoto } from '../layout-language/ai-layout-pictures';
25
25
  import { type AiSiteStarterList, type AiStockRefusedPicks } from '../model/ai-site-photos';
@@ -48,7 +48,11 @@ import type { AiJobStepContext, AiJobStepRunner } from './ai-job-text-step';
48
48
  * the datasets collection — with every rule the console's datasets route
49
49
  * has: the member's role and `data.manage`, `release_data_store`,
50
50
  * `dataStore` (Starter and up), `datasetsPerOrg` counted inside the create,
51
- * `recordsPerDataset` and the storage band.
51
+ * `recordsPerDataset` and the storage band. Each seeded record's
52
+ * reference and filter index is that writer's to stamp, with the data
53
+ * plugin's `datasetIntegrityFields` (`server/dataset-drafts.ts`), so this
54
+ * file writes no record of its own: its `'records'` is a listing's kind and
55
+ * its `values` a section group's.
52
56
  * - PICTURED where the plan lists it the way a gallery is listed (Zach,
53
57
  * 2026-10-10: a ceramic artist's "Selected work" came back as text-only
54
58
  * cards, where it had been an image-led gallery): a portfolio's pieces, a
@@ -96,6 +100,12 @@ export interface AiSiteDatasetInput {
96
100
  * list a visitor reads rather than looks at: questions, steps, hours.
97
101
  */
98
102
  pictures?: AiSiteDatasetPictures;
103
+ /**
104
+ * The sections of one page that each list part of it, and the value of a
105
+ * planned field each lists (AGL-4065, {@link aiSiteDatasetSplitOf}):
106
+ * absent where no page lists it twice.
107
+ */
108
+ split?: AiDatasetSplit;
99
109
  }
100
110
  /** What a dataset's records are pictured as: things or people. */
101
111
  export type AiSiteDatasetPictures = 'things' | 'people';
@@ -113,6 +123,20 @@ export declare function aiSiteDatasetPicturesOf(dataset: Pick<AiBuildPlanCreate,
113
123
  export declare const AI_SITE_DATASET_NOT_WRITTEN_COPY = "This dataset could not be added this time.";
114
124
  /** The note a written dataset carries on the job's page. */
115
125
  export declare function aiSiteDatasetNote(records: number): string;
126
+ /**
127
+ * The note a dataset that starts with samples carries (AGL-4065): the brief
128
+ * gave no entries for the list, so its rows are samples to replace.
129
+ */
130
+ export declare function aiSiteDatasetSampleNote(records: number, samples: number): string;
131
+ /**
132
+ * The note a dataset written with no records carries (AGL-4065): where its
133
+ * rows go. A list the brief gives no entries for starts with samples
134
+ * (`AI_DATASET_LIST_WITHOUT_ENTRIES`), so this is the one whose samples were
135
+ * placeholders even after the re-ask, or a start that makes none. Its band
136
+ * stays on every page that lists it — its heading, one honest line, and the
137
+ * live list the owner's first row fills.
138
+ */
139
+ export declare function aiSiteDatasetEmptyNote(name: string): string;
116
140
  /** The note a record template's row adds: the binding is the member's to save (rule 13). */
117
141
  export declare const AI_SITE_RECORD_PAGE_NOTE = "A page per record is drafted, not live: open Page Properties \u2192 Record pages to save it.";
118
142
  /** ASSUMED: what one dataset pass reads and writes beside its generation: the site, the writer's reads and its transaction. */
@@ -122,7 +146,32 @@ export declare const AI_SITE_DATASET_BUDGET: import("./ai-job-budget").AiJobStep
122
146
  /** The plan's datasets, in plan order, at most the most a start creates. */
123
147
  export declare function aiSitePlanDatasets(plan: Pick<AiJobPlan, 'create'>): AiBuildPlanCreate[];
124
148
  /** Where the plan lists a dataset, in words its design reads, whether a page is its record template, and the form that writes to it. */
125
- export declare function aiSiteDatasetInputOf(dataset: Pick<AiBuildPlanCreate, 'name'>, screens: readonly AiBuildPlanScreen[], create?: readonly AiBuildPlanCreate[]): AiSiteDatasetInput;
149
+ export declare function aiSiteDatasetInputOf(dataset: Pick<AiBuildPlanCreate, 'name'> & Partial<Pick<AiBuildPlanCreate, 'fields'>>, screens: readonly AiBuildPlanScreen[], create?: readonly AiBuildPlanCreate[]): AiSiteDatasetInput;
150
+ /**
151
+ * The value of a dataset's planned field each of a page's sections lists
152
+ * (AGL-4065), where the page lists the dataset in two sections or more: the
153
+ * words of each section's name that are not the dataset's own (its name, its
154
+ * fields) nor framing — "Upcoming races" and "Past results" over "Race
155
+ * events", whose fields include Result and Status, are "Upcoming" and
156
+ * "Past". `null` where the dataset plans no field that files its records
157
+ * (a Status, a Category, a Type…), or a section names no value of its own,
158
+ * or two name the same. Pure.
159
+ */
160
+ export declare function aiSiteDatasetSplitOf(dataset: {
161
+ name: string;
162
+ fields: readonly string[];
163
+ }, sections: readonly string[]): AiDatasetSplit | null;
164
+ /**
165
+ * A site start's plan with each page that lists one of its new datasets in
166
+ * two sections or more, where nothing can tell those sections' records apart
167
+ * ({@link aiSiteDatasetSplitOf}), listing it in one (AGL-4065): the first
168
+ * section keeps its place and takes the others' names and the most items
169
+ * any of them planned, and the others come off. Two lists of one dataset on
170
+ * one page would otherwise show the same records twice: the platform's
171
+ * repeat can filter by a field's value, and has no "the next five". A plan
172
+ * that needs no fold comes back as it was. Pure.
173
+ */
174
+ export declare function aiSettlePlanSplitListings<P extends Pick<AiBuildPlan, 'screens' | 'create'>>(plan: P): P;
126
175
  /** Whether a plan screen is the record template of a dataset the plan creates, and which. */
127
176
  export declare function aiSiteRecordTemplateOf(screen: Pick<AiBuildPlanScreen, 'record'>): string | null;
128
177
  /** The most fields a form's dataset is designed with: the data plugin writer's own ceiling is higher. */
@@ -336,6 +385,7 @@ export declare function aiDatasetListingsOf(datasets: ReadonlyArray<{
336
385
  groups?: {
337
386
  field: string;
338
387
  values: readonly string[];
388
+ expected?: readonly string[];
339
389
  } | null;
340
390
  }>, screens: readonly AiLayoutListingScreen[]): AiLayoutListing[];
341
391
  /**