@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235

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 (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -19,17 +19,19 @@ import { resolveOrgIdForHost } from "@aglyn/tenant-data-admin/server/organizatio
19
19
  import { isAiPlanNewRef } from "../model/ai-build-plan.js";
20
20
  import { aiBuildDegradation, aiBuildInitialLedger, aiBuildItemDelivered, aiBuildItemOpen, aiBuildNextUnit } from "../model/ai-build-job.js";
21
21
  import { aiBuildBuiltRefs, aiUnitErrorRetryable, aiUnitFailure, aiUnitSpend } from "./ai-build-unit-outcome.js";
22
- import { AI_SITE_EMAIL_TYPE, AI_SITE_MAX_SECTIONS, AI_SITE_PAGES, aiSiteNameSentence, aiSitePagesRefusal, aiSitePlanRefusal, aiSiteSubmissions, aiSiteWords, parseAiSiteJobInputs } from "../model/ai-site-job.js";
22
+ import { AI_SITE_EMAIL_TYPE, AI_SITE_MAX_SECTIONS, AI_SITE_PAGES, aiFreeSiteShortfall, aiFreeSiteShortfallText, aiSiteNameSentence, aiSitePagesRefusal, aiSitePlanRefusal, aiSiteSubmissions, aiSiteWords, parseAiSiteJobInputs, aiSiteBlogNavPage, aiSiteIsBlogStandIn, aiSitePlanWithoutBlogStandIns } from "../model/ai-site-job.js";
23
+ import { readFreeAiCreditsLeft } from "../usage/free-ai-credits-left.js";
23
24
  import { AI_SITE_SEO_OUTPUT_ID, aiSiteSeoProposalForInputs } from "../model/ai-site-start-seo.js";
24
25
  import { aiModelForStep } from "../providers/routing.js";
25
26
  import { registerAiJobAdmission } from "./ai-job-admission.js";
26
27
  import { aiOriginJobId, aiRecordedJobDraftId } from "./ai-job-draft-ids.js";
27
28
  import { readAiDraftNodes } from "./ai-job-drafts.js";
28
- import { AI_LAYOUT_SITE_PAGES_INPUT, aiLayoutSitePagesOfPlan } from "./ai-job-layout-site-pages.js";
29
+ import { AI_LAYOUT_SITE_ALIASES_INPUT, AI_LAYOUT_SITE_PAGES_INPUT, AI_LAYOUT_SITE_PAGES_MAX, aiLayoutIsHomeSlug, aiLayoutSitePagesOfPlan } from "./ai-job-layout-site-pages.js";
29
30
  import { aiPageSectionNodeId } from "./ai-job-page-sections.js";
31
+ import { AI_LAYOUT_LISTINGS_INPUT } from "../layout-language/ai-layout-listings.js";
30
32
  import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOfPlan } from "./ai-job-page-language.js";
31
33
  import { aiJobPublishesSite, aiPublishGuidedSite } from "./ai-site-publish.js";
32
- import { AI_SITE_CONTENT_INPUT, AI_SITE_POST_BUDGET, AI_SITE_POSTS, AI_SITE_POSTS_LABEL, AI_SITE_PRODUCTS_LABEL, aiPublishSitePosts, aiSiteContentBriefLines, aiSiteContentPart, aiSiteContentRefusal, aiSiteProductsBriefLine, createAiSiteProductsRunner, runAiSitePostsUnit } from "./ai-job-site-content.js";
34
+ import { AI_SITE_CONTENT_INPUT, AI_SITE_POST_BUDGET, AI_SITE_POSTS, AI_SITE_POSTS_LABEL, AI_SITE_PRODUCTS_LABEL, AI_SITE_PRODUCTS_PRICE_NOTE, aiPublishSitePosts, aiSiteContentBriefLines, aiSiteContentPart, aiSiteContentRefusal, aiSiteListings, aiSiteProductsBriefLine, createAiSiteProductsRunner, runAiSitePostsUnit } from "./ai-job-site-content.js";
33
35
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
34
36
  import { AI_SITE_LOOK_BUDGET, aiRunSiteLook } from "./ai-job-site-look.js";
35
37
  import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
@@ -429,39 +431,79 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
429
431
  // ids are minted on the plan, and the platform writes the header's links.
430
432
  // A page is told them too, so its buttons may go to a page built after it.
431
433
  if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {
432
- // A guided start links every page the person asked for (AGL-3660).
433
- const pages = aiLayoutSitePagesOfPlan(plan.screens, {
434
+ var _job_outputs1;
435
+ // A guided start links every page the person asked for (AGL-3660), and
436
+ // the blog its first posts are written into, by its path, second after
437
+ // Home (AGL-3676): the live Slow Roads start linked an "Articles" page
438
+ // and never the blog.
439
+ // A planned page standing in for that blog — "Articles", "Journal" — is
440
+ // merged into it (AGL-3676): not linked, not built, and every link to it
441
+ // goes to the blog, under words that name the blog.
442
+ const writesPosts = aiSiteWritesPosts(job);
443
+ const merged = writesPosts ? aiSitePlanWithoutBlogStandIns(plan.screens) : {
444
+ screens: plan.screens,
445
+ standIns: []
446
+ };
447
+ const planned = aiLayoutSitePagesOfPlan(merged.screens, {
434
448
  guided: true
435
449
  });
450
+ const blogPage = aiSiteBlogNavPage(merged.screens);
451
+ const blog = writesPosts ? [
452
+ blogPage
453
+ ] : [];
454
+ const homes = planned.filter((page)=>aiLayoutIsHomeSlug(page.slug));
455
+ const pages = [
456
+ ...homes,
457
+ ...blog,
458
+ ...planned.filter((page)=>!aiLayoutIsHomeSlug(page.slug))
459
+ ].slice(0, AI_LAYOUT_SITE_PAGES_MAX);
436
460
  if (pages.length) unitInputs[AI_LAYOUT_SITE_PAGES_INPUT] = pages;
461
+ const aliases = merged.standIns.filter((screen)=>typeof screen.id === 'string' && !!screen.id).map((screen)=>({
462
+ id: screen.id,
463
+ label: blogPage.label,
464
+ slug: blogPage.slug,
465
+ href: blogPage.href,
466
+ standsInFor: screen.title
467
+ }));
468
+ if (aliases.length) unitInputs[AI_LAYOUT_SITE_ALIASES_INPUT] = aliases;
469
+ // The store's catalog and the blog, listed by the sections that show them,
470
+ // and the cart in a selling site's header (AGL-3676).
471
+ const listings = aiSiteListings({
472
+ outputs: (_job_outputs1 = job.outputs) != null ? _job_outputs1 : [],
473
+ screens: merged.screens,
474
+ sells: aiSiteSellsProducts(job)
475
+ });
476
+ if (listings.length) unitInputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
437
477
  }
438
478
  // A site's pages and its layout are designed in the layout language and
439
479
  // compiled (AGL-3660), and a page is told which page places the site's form.
440
480
  if (unit.kind === 'layout' || unit.kind === 'page') {
441
- var _job_outputs1;
481
+ var _job_outputs2;
442
482
  var _look_proposal;
443
483
  // The look designed first (AGL-3660): its header arrangement and band rhythm.
444
- const look = ((_job_outputs1 = job.outputs) != null ? _job_outputs1 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
484
+ const look = ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
445
485
  const style = look == null ? void 0 : (_look_proposal = look.proposal) == null ? void 0 : _look_proposal['style'];
446
486
  if (style) unitInputs['siteStyle'] = {
447
487
  headerAlign: style['headerAlign'],
448
- rhythm: style['rhythm']
488
+ rhythm: style['rhythm'],
489
+ seed: style['seed']
449
490
  };
450
491
  unitInputs[AI_LAYOUT_LANGUAGE_INPUT] = true;
451
492
  const formPage = aiLayoutFormPageOfPlan(plan);
452
493
  if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
453
494
  }
454
495
  if (unit.kind === 'posts') {
455
- var _job_outputs2, _inputs_businessName;
496
+ var _job_outputs3, _inputs_businessName;
456
497
  // The posts already written (one a pass), the addresses the site's own
457
498
  // pages answer at, and the byline the person gave (AGL-3676).
458
499
  const posts = {
459
- written: ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).filter((output)=>output.resource === 'entry').map((output)=>({
500
+ written: ((_job_outputs3 = job.outputs) != null ? _job_outputs3 : []).filter((output)=>output.resource === 'entry').map((output)=>({
460
501
  id: output.id,
461
502
  title: output.label
462
503
  })),
463
504
  total: AI_SITE_POSTS,
464
- avoidSlugs: plan.screens.map((screen)=>screen.slug.replace(/^\/+/, '').split('/')[0]).filter(Boolean),
505
+ // A page standing in for the blog is merged into it, so its address is the blog's to take.
506
+ avoidSlugs: aiSitePlanWithoutBlogStandIns(plan.screens).screens.map((screen)=>screen.slug.replace(/^\/+/, '').split('/')[0]).filter(Boolean),
465
507
  byline: typeof inputs === 'string' ? '' : (_inputs_businessName = inputs.businessName) != null ? _inputs_businessName : ''
466
508
  };
467
509
  unitInputs[AI_SITE_CONTENT_INPUT] = posts;
@@ -484,52 +526,93 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
484
526
  brief: brief.join('\n').slice(0, AI_JOB_BRIEF_MAX_CHARS)
485
527
  });
486
528
  }
529
+ /**
530
+ * Whether this site start writes its first posts (AGL-3676): its ledger owes
531
+ * the posts part and has not given up on it. The ledger is written before the
532
+ * layout is built, so the header knows the blog before it exists.
533
+ */ export function aiSiteWritesPosts(job) {
534
+ var _job_items;
535
+ return ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.slot === 'posts' && row.status !== 'skipped' && row.status !== 'failed');
536
+ }
537
+ /** What the row of a planned page merged into the blog says (AGL-3676). */ export const AI_SITE_BLOG_MERGED_NOTE = 'Your blog takes this page’s place: its posts are listed there, and its links go to it.';
538
+ /**
539
+ * Whether this site start sells (AGL-3676): its ledger owes the store's first
540
+ * products and has not given up on them, so the layout built before them
541
+ * carries the cart.
542
+ */ export function aiSiteSellsProducts(job) {
543
+ var _job_items;
544
+ return ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.slot === 'products' && row.status !== 'skipped' && row.status !== 'failed');
545
+ }
487
546
  /**
488
547
  * A scaffold is admitted with inputs that read, for a site of the job's own
489
548
  * org, and only where this deployment has loaded the step that builds a page:
490
549
  * a scaffold whose pages nothing can build is not a scaffold.
491
- */ export const aiSiteJobAdmission = async (context)=>{
492
- const inputs = parseAiSiteJobInputs(context.inputs);
493
- if (typeof inputs === 'string') return {
494
- status: 400,
495
- error: inputs
496
- };
497
- // The workspace's own page band (AGL-3594): one or two pages on the Free
498
- // taste, four to eight on a paid plan.
499
- const freeTaste = aiSiteFreeTaste(context.org);
500
- const pages = aiSitePagesRefusal(inputs.pages, freeTaste);
501
- if (pages) return {
502
- status: 400,
503
- error: pages
504
- };
505
- if (context.plan) {
506
- const shape = aiSitePlanRefusal(context.plan, {
507
- freeTaste
508
- });
509
- if (shape) return {
550
+ */ export function createAiSiteJobAdmission(deps = {}) {
551
+ var _deps_freeCreditsLeft, _deps_now;
552
+ const freeCreditsLeft = (_deps_freeCreditsLeft = deps.freeCreditsLeft) != null ? _deps_freeCreditsLeft : readFreeAiCreditsLeft;
553
+ const now = (_deps_now = deps.now) != null ? _deps_now : ()=>new Date();
554
+ return async (context)=>{
555
+ const inputs = parseAiSiteJobInputs(context.inputs);
556
+ if (typeof inputs === 'string') return {
510
557
  status: 400,
511
- error: shape
558
+ error: inputs
512
559
  };
513
- }
514
- if (!context.hostId) {
515
- return {
560
+ // The workspace's own page band (AGL-3594): one or two pages on the Free
561
+ // taste, four to eight on a paid plan.
562
+ const freeTaste = aiSiteFreeTaste(context.org);
563
+ const pages = aiSitePagesRefusal(inputs.pages, freeTaste);
564
+ if (pages) return {
516
565
  status: 400,
517
- error: 'Open the site the scaffold is for before starting the job'
566
+ error: pages
518
567
  };
519
- }
520
- const owner = await resolveOrgIdForHost(context.hostId);
521
- if (!owner || owner !== context.orgId) return {
522
- status: 404,
523
- error: 'Unknown site'
524
- };
525
- if (!aiJobStepRunnerFor('page')) {
526
- return {
527
- status: 400,
528
- error: AI_SITE_NO_PAGE_STEP_COPY
568
+ // A Free start that what is left of the month's Free credits cannot pay for
569
+ // is refused before it spends (AGL-3660), on the figure the dialog quotes —
570
+ // the dialog asks the same, and this is what a stale dialog meets. Only at
571
+ // creation: a resume is the same job carrying on from where it paused.
572
+ if (freeTaste && !context.plan) {
573
+ const credits = await freeCreditsLeft(context.firestore, {
574
+ orgId: context.orgId,
575
+ org: context.org,
576
+ now: now()
577
+ });
578
+ const shortfall = aiFreeSiteShortfall(credits, inputs.pages);
579
+ if (credits && shortfall) {
580
+ return {
581
+ status: 429,
582
+ error: aiFreeSiteShortfallText(shortfall, credits.resetsOn)
583
+ };
584
+ }
585
+ }
586
+ if (context.plan) {
587
+ const shape = aiSitePlanRefusal(context.plan, {
588
+ freeTaste
589
+ });
590
+ if (shape) return {
591
+ status: 400,
592
+ error: shape
593
+ };
594
+ }
595
+ if (!context.hostId) {
596
+ return {
597
+ status: 400,
598
+ error: 'Open the site the scaffold is for before starting the job'
599
+ };
600
+ }
601
+ const owner = await resolveOrgIdForHost(context.hostId);
602
+ if (!owner || owner !== context.orgId) return {
603
+ status: 404,
604
+ error: 'Unknown site'
529
605
  };
530
- }
531
- return null;
532
- };
606
+ if (!aiJobStepRunnerFor('page')) {
607
+ return {
608
+ status: 400,
609
+ error: AI_SITE_NO_PAGE_STEP_COPY
610
+ };
611
+ }
612
+ return null;
613
+ };
614
+ }
615
+ export const aiSiteJobAdmission = createAiSiteJobAdmission();
533
616
  /** Whether a scaffold's workspace spends the Free taste (AGL-3594); a missing org reads as paid, as the band has always been. */ export function aiSiteFreeTaste(org) {
534
617
  return Boolean(org) && resolveEffectivePlan(org) === 'free';
535
618
  }
@@ -709,12 +792,18 @@ export function createAiJobSiteStep(deps = {}) {
709
792
  ]));
710
793
  const next = aiBuildNextUnit(ledgerUnits, ledger);
711
794
  /** A guided start's last pass puts what it built on the site, once (AGL-3596): only its pages that were built. */ const finish = async (outcome, pages)=>{
795
+ var _postsRow_outputs;
712
796
  if (!aiJobPublishesSite(job) || job.sitePublish || !job.hostId || !pages.length) return outcome;
713
- const sitePublish = await publish(context.firestore, {
797
+ // A blog the header links by path before its posts exist (AGL-3660):
798
+ // owed and not delivered, its links come out of what is published.
799
+ const postsRow = rows.get('posts');
800
+ const sitePublish = await publish(context.firestore, _extends({
714
801
  job,
715
802
  outputs: pages,
716
803
  now: context.now
717
- }).catch((error)=>{
804
+ }, postsRow ? {
805
+ blogUnwritten: !aiBuildItemDelivered(postsRow) || !((_postsRow_outputs = postsRow.outputs) == null ? void 0 : _postsRow_outputs.length)
806
+ } : {})).catch((error)=>{
718
807
  // The site is built either way; the pages stay drafts and say so.
719
808
  console.error('ai site publish threw', {
720
809
  orgId: job.orgId,
@@ -807,6 +896,16 @@ export function createAiJobSiteStep(deps = {}) {
807
896
  // A blog's posts and a store's products ask, before their first pass,
808
897
  // whether this member may have them here (AGL-3676): a refusal spends
809
898
  // nothing, and the row says why, as a build's item does.
899
+ // A planned page standing in for the blog this start writes is merged into
900
+ // it (AGL-3676): not built, its address the blog's, its links the blog's.
901
+ // The plan step asks for a plan without one only once.
902
+ if (unit.kind === 'page' && unit.screen && aiSiteIsBlogStandIn(unit.screen) && aiSiteWritesPosts(job)) {
903
+ return closing(settle({
904
+ slot: unit.slot,
905
+ status: 'skipped',
906
+ note: AI_SITE_BLOG_MERGED_NOTE
907
+ }));
908
+ }
810
909
  if ((unit.kind === 'posts' || unit.kind === 'products') && ((_rows_get = rows.get(unit.slot)) == null ? void 0 : _rows_get.status) !== 'running') {
811
910
  const refusal = await contentRefusal(unit.kind, _extends({}, context, {
812
911
  job
@@ -900,7 +999,9 @@ export function createAiJobSiteStep(deps = {}) {
900
999
  slot: unit.slot,
901
1000
  status: degraded ? 'degraded' : 'succeeded',
902
1001
  outputs: outcome.outputs.map((output)=>output.id),
903
- note
1002
+ // A store's products are listed before they are priced (AGL-3676):
1003
+ // the row says what is left before the store sells.
1004
+ note: unit.kind === 'products' && outcome.outputs.length ? AI_SITE_PRODUCTS_PRICE_NOTE : note
904
1005
  }, degraded ? {
905
1006
  degradedBy: degradation.degradedBy
906
1007
  } : {}), spent);