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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +17 -0
  3. package/src/lib/components/ai-job-links.js +63 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-job-plan.component.js +8 -1
  6. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  7. package/src/lib/components/ai-site-build-page.component.js +7 -1
  8. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  9. package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
  10. package/src/lib/components/ai-store-finish-card.component.js +116 -0
  11. package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
  12. package/src/lib/components/assist-build-card.component.js +12 -1
  13. package/src/lib/components/assist-build-card.component.js.map +1 -1
  14. package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
  15. package/src/lib/jobs/ai-job-build-step.js +283 -7
  16. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
  18. package/src/lib/jobs/ai-job-drafts.js +4 -1
  19. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  21. package/src/lib/jobs/ai-job-layout-language.js +4 -2
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +3 -1
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  26. package/src/lib/jobs/ai-job-page-language.js +19 -7
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
  29. package/src/lib/jobs/ai-job-page-sections.js +40 -3
  30. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  31. package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
  32. package/src/lib/jobs/ai-job-page-step.js +19 -8
  33. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
  35. package/src/lib/jobs/ai-job-plan-step.js +41 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.js +3 -2
  38. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  39. package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
  40. package/src/lib/jobs/ai-job-site-datasets.js +206 -13
  41. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  42. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  43. package/src/lib/jobs/ai-job-site-step.js +109 -22
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  46. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  47. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  48. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  49. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  50. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  51. package/src/lib/jobs/ai-layout-stock-photos.d.ts +129 -8
  52. package/src/lib/jobs/ai-layout-stock-photos.js +919 -75
  53. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  54. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  55. package/src/lib/jobs/ai-site-publish.js +76 -15
  56. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  57. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  58. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  59. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  60. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.d.ts +39 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js +508 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  64. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  65. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  66. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  67. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  68. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  69. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  70. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  71. package/src/lib/model/ai-build-job.d.ts +2 -0
  72. package/src/lib/model/ai-build-job.js +23 -3
  73. package/src/lib/model/ai-build-job.js.map +1 -1
  74. package/src/lib/model/ai-build-plan.d.ts +7 -0
  75. package/src/lib/model/ai-build-plan.js.map +1 -1
  76. package/src/lib/model/ai-build-progress.js +3 -1
  77. package/src/lib/model/ai-build-progress.js.map +1 -1
  78. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  79. package/src/lib/model/ai-build-store-pages.js +209 -0
  80. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  81. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  82. package/src/lib/model/ai-layout-store-links.js +165 -0
  83. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  84. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  85. package/src/lib/model/ai-site-inventory.js.map +1 -1
  86. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  87. package/src/lib/model/ai-site-store-pages.js +383 -0
  88. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  89. package/src/lib/runtime/site-inventory.js +8 -3
  90. package/src/lib/runtime/site-inventory.js.map +1 -1
  91. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  92. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -25,6 +25,7 @@ import { aiPublishGuidedSite } from './ai-site-publish';
25
25
  import { aiPublishSitePosts, aiSiteContentRefusal } from './ai-job-site-content';
26
26
  import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
27
27
  import { aiSiteDatasetRefusal } from './ai-job-site-datasets';
28
+ import { aiSiteStorePagesRefusal, type AiSiteStorePagesInput } from './ai-job-site-store-pages';
28
29
  import { type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step';
29
30
  import { aiJobStepRunnerFor } from './ai-jobs';
30
31
  /**
@@ -85,9 +86,11 @@ import { aiJobStepRunnerFor } from './ai-jobs';
85
86
  * page job's. The site's datasets (AGL-3616) are built after the layout and
86
87
  * before the form, which may write its submissions to one; a blog's first
87
88
  * posts and a store's first products (AGL-3676) after the form; all of them
88
- * before the pages, which are told what they are.
89
+ * before the pages, which are told what they are. A paid store's account,
90
+ * cart and policy pages (`store`, AGL-3676) come after its planned pages,
91
+ * which keep the first claim on the site's pages allowance.
89
92
  */
90
- export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'dataset' | 'posts' | 'products' | 'page' | 'email';
93
+ export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'dataset' | 'posts' | 'products' | 'page' | 'store' | 'email';
91
94
  export interface AiSiteUnit {
92
95
  kind: AiSiteUnitKind;
93
96
  /** The job kind whose registered runner builds it. */
@@ -224,6 +227,14 @@ export declare function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit:
224
227
  * for leaving it out.
225
228
  */
226
229
  export declare function aiSiteUnitJob(job: AiJob, unit: AiSiteUnit, built: BuiltRefs): AiJob;
230
+ /** What a store pages unit writes (AGL-3676): the plan's store pages, their words' links, and the layout. */
231
+ export declare function aiSiteStorePagesInputOf(plan: Pick<AiJobPlan, 'screens'>, built: BuiltRefs): AiSiteStorePagesInput;
232
+ /**
233
+ * Whether this site start adds a store's own pages (AGL-3676): its ledger
234
+ * owes them and has not given up on them. The ledger is written before the
235
+ * layout is built, so the header links the account before the page exists.
236
+ */
237
+ export declare function aiSiteAddsStorePages(job: Pick<AiJob, 'items'>): boolean;
227
238
  /**
228
239
  * Whether this site start writes its first posts (AGL-3676): its ledger owes
229
240
  * the posts part and has not given up on it. The ledger is written before the
@@ -291,6 +302,9 @@ export interface AiJobSiteStepDeps {
291
302
  products?: AiJobStepRunner;
292
303
  /** Whether a part may be built for this member, before its first pass. */
293
304
  contentRefusal?: typeof aiSiteContentRefusal;
305
+ /** The store's own pages' pass and whether they may be added here (AGL-3676); specs hand in fakes. */
306
+ storePages?: AiJobStepRunner;
307
+ storePagesRefusal?: typeof aiSiteStorePagesRefusal;
294
308
  /** A dataset's pass and whether one may be made here (AGL-3616); specs hand in fakes. */
295
309
  dataset?: AiJobStepRunner;
296
310
  datasetRefusal?: typeof aiSiteDatasetRefusal;
@@ -328,6 +342,8 @@ export declare function aiSiteLedgerUnits(units: readonly AiSiteUnit[]): AiBuild
328
342
  export declare function aiSiteInitialLedger(units: readonly AiSiteUnit[], outputs: readonly AiJobOutput[]): AiJobItemLedger[];
329
343
  export declare function createAiJobSiteStep(deps?: AiJobSiteStepDeps): AiJobStepRunner;
330
344
  export declare const runAiJobSiteStep: AiJobStepRunner;
345
+ /** ASSUMED: what the store pages' pass needs — five draft transactions and their search listings, no model. */
346
+ export declare const AI_SITE_STORE_PAGES_MINIMUM_MS = 15000;
331
347
  /**
332
348
  * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no
333
349
  * model itself: each pass hands one unit to the step registered for its kind,
@@ -28,17 +28,19 @@ import { aiModelForStep } from "../providers/routing.js";
28
28
  import { registerAiJobAdmission } from "./ai-job-admission.js";
29
29
  import { aiOriginJobId, aiRecordedJobDraftId } from "./ai-job-draft-ids.js";
30
30
  import { readAiDraftNodes } from "./ai-job-drafts.js";
31
- import { AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
31
+ import { AI_STOCK_PHOTO_AVOID_INPUT, AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
32
32
  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";
33
33
  import { aiPageSectionNodeId } from "./ai-job-page-sections.js";
34
- import { AI_LAYOUT_LISTINGS_INPUT } from "../layout-language/ai-layout-listings.js";
34
+ import { AI_LAYOUT_LISTINGS_INPUT, aiLayoutIsShopPage } from "../layout-language/ai-layout-listings.js";
35
35
  import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
36
36
  import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOfPlan } from "./ai-job-page-language.js";
37
37
  import { aiJobPublishesSite, aiPublishGuidedSite } from "./ai-site-publish.js";
38
38
  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";
39
39
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
40
- import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
40
+ import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetPlacedPhotos, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
41
41
  import { AI_SITE_LOOK_BUDGET, aiRunSiteLook } from "./ai-job-site-look.js";
42
+ import { AI_SITE_STORE_PAGES_INPUT, aiSiteStorePagesRefusal, runAiSiteStorePagesUnit } from "./ai-job-site-store-pages.js";
43
+ import { AI_SITE_STORE_LINKS_INPUT, AI_SITE_STORE_PAGES_LABEL, AI_SITE_STORE_PAGES_NOTE, aiStoreFrameLinks, aiStorePagesOfPlan } from "../model/ai-site-store-pages.js";
42
44
  import { AI_FORM_DATASET_MADE_INPUT } from "./ai-job-form-dataset.js";
43
45
  import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
44
46
  import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
@@ -85,6 +87,11 @@ import { aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerA
85
87
  dataset: {
86
88
  jobKind: 'text',
87
89
  resource: 'draft'
90
+ },
91
+ // The scaffold's own runner writes them, by code (`ai-job-site-store-pages.ts`).
92
+ store: {
93
+ jobKind: 'text',
94
+ resource: 'screen'
88
95
  }
89
96
  };
90
97
  /** The unit kind a creation is built as, where a unit builds it. */ const CREATION_UNIT_KINDS = {
@@ -131,7 +138,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
131
138
  * (one a pass, the largest part) and a welcome email — with nothing to
132
139
  * spare, so a runner that never finishes is still bounded while a real site
133
140
  * is not.
134
- */ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_DATASETS_MAX + AI_SITE_POSTS + 1;
141
+ */ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_DATASETS_MAX + AI_SITE_POSTS + 2;
135
142
  /**
136
143
  * The units a plan implies, in build order: the palette first, because a
137
144
  * member reads it while the pages are still building; then the layout every
@@ -202,6 +209,15 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
202
209
  label: screen.title
203
210
  }));
204
211
  });
212
+ // A paid store's own pages (AGL-3676): outside the plan's pages, after them.
213
+ if (options.content === 'products') {
214
+ units.push(_extends({
215
+ kind: 'store'
216
+ }, UNIT_KINDS.store, {
217
+ slot: 'store',
218
+ label: AI_SITE_STORE_PAGES_LABEL
219
+ }));
220
+ }
205
221
  if (options.welcomeEmail) {
206
222
  units.push(_extends({
207
223
  kind: 'email'
@@ -464,15 +480,18 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
464
480
  // A page is told the pages built before it, whose photos it does not place
465
481
  // again (AGL-3660): the maker's portrait in About is never a vase in Work.
466
482
  if (unit.screen) {
467
- var _job_outputs2;
483
+ var _job_outputs2, _job_outputs3;
468
484
  const pages = ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).filter((output)=>output.resource === 'screen' && output.hostId === job.hostId && output.id).map((output)=>output.id);
469
485
  if (pages.length) unitInputs[AI_STOCK_PHOTO_PAGES_INPUT] = pages;
486
+ // Nor a photo the site's datasets' records already show (AGL-3616).
487
+ const records = aiSiteDatasetPlacedPhotos((_job_outputs3 = job.outputs) != null ? _job_outputs3 : []);
488
+ if (records.length) unitInputs[AI_STOCK_PHOTO_AVOID_INPUT] = records;
470
489
  }
471
490
  // The layout is built before the pages, so it is told them (AGL-3596): their
472
491
  // ids are minted on the plan, and the platform writes the header's links.
473
492
  // A page is told them too, so its buttons may go to a page built after it.
474
493
  if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {
475
- var _job_outputs3, _job_outputs4;
494
+ var _job_outputs4, _job_outputs5;
476
495
  var _aiSiteKindOfInputs, _aiSiteKindOfInputs1;
477
496
  // A guided start links every page the person asked for (AGL-3660), and
478
497
  // the blog its first posts are written into, by its path, second after
@@ -511,7 +530,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
511
530
  // The store's catalog and the blog, listed by the sections that show them,
512
531
  // and the cart in a selling site's header (AGL-3676).
513
532
  const listings = aiSiteListings({
514
- outputs: (_job_outputs3 = job.outputs) != null ? _job_outputs3 : [],
533
+ outputs: (_job_outputs4 = job.outputs) != null ? _job_outputs4 : [],
515
534
  screens: merged.screens,
516
535
  sells: aiSiteSellsProducts(job),
517
536
  // A store lists its catalog on its Shop page even when its first
@@ -528,20 +547,28 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
528
547
  entry.id
529
548
  ]));
530
549
  listings.push(...aiSiteDatasetListings({
531
- outputs: (_job_outputs4 = job.outputs) != null ? _job_outputs4 : [],
550
+ outputs: (_job_outputs5 = job.outputs) != null ? _job_outputs5 : [],
532
551
  datasets: aiSitePlanDatasets(plan),
533
552
  screens: merged.screens,
534
553
  delivered
535
554
  }));
536
555
  if (listings.length) unitInputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
556
+ // A paid store's header links its account beside the cart, and its footer
557
+ // its account and policies (AGL-3676), by path: they are written after.
558
+ if (unit.kind === 'layout' && aiSiteAddsStorePages(job)) {
559
+ unitInputs[AI_SITE_STORE_LINKS_INPUT] = aiStoreFrameLinks(aiStorePagesOfPlan(merged.screens));
560
+ }
537
561
  }
562
+ // The store's own pages (AGL-3676): which to write, what their words link,
563
+ // and the layout they render inside.
564
+ if (unit.kind === 'store') unitInputs[AI_SITE_STORE_PAGES_INPUT] = aiSiteStorePagesInputOf(plan, built);
538
565
  // A site's pages and its layout are designed in the layout language and
539
566
  // compiled (AGL-3660), and a page is told which page places the site's form.
540
567
  if (unit.kind === 'layout' || unit.kind === 'page') {
541
- var _job_outputs5;
568
+ var _job_outputs6;
542
569
  var _look_proposal;
543
570
  // The look designed first (AGL-3660): its header arrangement and band rhythm.
544
- const look = ((_job_outputs5 = job.outputs) != null ? _job_outputs5 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
571
+ const look = ((_job_outputs6 = job.outputs) != null ? _job_outputs6 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
545
572
  const style = look == null ? void 0 : (_look_proposal = look.proposal) == null ? void 0 : _look_proposal['style'];
546
573
  if (style) unitInputs['siteStyle'] = {
547
574
  headerAlign: style['headerAlign'],
@@ -553,11 +580,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
553
580
  if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
554
581
  }
555
582
  if (unit.kind === 'posts') {
556
- var _job_outputs6, _inputs_businessName;
583
+ var _job_outputs7, _inputs_businessName;
557
584
  // The posts already written (one a pass), the addresses the site's own
558
585
  // pages answer at, and the byline the person gave (AGL-3676).
559
586
  const posts = {
560
- written: ((_job_outputs6 = job.outputs) != null ? _job_outputs6 : []).filter((output)=>output.resource === 'entry').map((output)=>({
587
+ written: ((_job_outputs7 = job.outputs) != null ? _job_outputs7 : []).filter((output)=>output.resource === 'entry').map((output)=>({
561
588
  id: output.id,
562
589
  title: output.label
563
590
  })),
@@ -594,6 +621,30 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
594
621
  brief: brief.join('\n').slice(0, AI_JOB_BRIEF_MAX_CHARS)
595
622
  });
596
623
  }
624
+ /** What a store pages unit writes (AGL-3676): the plan's store pages, their words' links, and the layout. */ export function aiSiteStorePagesInputOf(plan, built) {
625
+ var _path, _ref;
626
+ var _pages_find;
627
+ const pages = aiStorePagesOfPlan(plan.screens);
628
+ const path = (screen)=>screen ? `/${screen.slug.trim().replace(/^\/+/, '').split('/')[0].toLowerCase()}` : null;
629
+ const others = plan.screens.filter((screen)=>!screen.record && !aiLayoutIsHomeSlug(screen.slug));
630
+ return {
631
+ pages,
632
+ facts: {
633
+ contactPath: path(others.find((screen)=>/\b(contact|get in touch|visit us)\b/i.test(`${screen.slug.replace(/-/g, ' ')} ${screen.title}`))),
634
+ shopPath: (_path = path(others.find((screen)=>aiLayoutIsShopPage(screen)))) != null ? _path : '/shop',
635
+ shippingPath: (_ref = (_pages_find = pages.find((page)=>page.key === 'shipping')) == null ? void 0 : _pages_find.href) != null ? _ref : '/shipping-returns'
636
+ },
637
+ layoutId: aiSiteBuiltLayoutId(built)
638
+ };
639
+ }
640
+ /**
641
+ * Whether this site start adds a store's own pages (AGL-3676): its ledger
642
+ * owes them and has not given up on them. The ledger is written before the
643
+ * layout is built, so the header links the account before the page exists.
644
+ */ export function aiSiteAddsStorePages(job) {
645
+ var _job_items;
646
+ return ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.slot === 'store' && row.status !== 'skipped' && row.status !== 'failed');
647
+ }
597
648
  /**
598
649
  * Whether this site start writes its first posts (AGL-3676): its ledger owes
599
650
  * the posts part and has not given up on it. The ledger is written before the
@@ -794,7 +845,7 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
794
845
  }).map((unit)=>unit.slot);
795
846
  return units.map((unit)=>_extends({
796
847
  slot: unit.slot,
797
- op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' || unit.kind === 'dataset' ? unit.kind : unit.jobKind,
848
+ op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' || unit.kind === 'dataset' || unit.kind === 'store' ? unit.kind : unit.jobKind,
798
849
  label: unit.label
799
850
  }, unit.creation ? {
800
851
  creation: unit.creation
@@ -833,24 +884,29 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
833
884
  });
834
885
  }
835
886
  /** The units a scaffold owes on this job: what its plan implies that this deployment can build. */ function aiSiteOwedUnits(job, plan, inputs, freeTaste, runnerFor) {
887
+ var _job_items;
888
+ // A start whose ledger was written before the store pages existed (AGL-3676)
889
+ // is not owed them: its ledger has no row for them to settle.
890
+ const ledgered = !((_job_items = job.items) == null ? void 0 : _job_items.length) || job.items.some((row)=>row.slot === 'store');
836
891
  return aiSiteJobUnits(plan, {
837
892
  welcomeEmail: aiSiteWelcomeEmail(inputs, freeTaste),
838
893
  content: aiSiteContentPart(job.inputs, freeTaste)
839
- }).filter((unit)=>unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || runnerFor(unit.jobKind));
894
+ }).filter((unit)=>unit.kind === 'store' && ledgered || unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || unit.kind !== 'store' && runnerFor(unit.jobKind));
840
895
  }
841
896
  export function createAiJobSiteStep(deps = {}) {
842
- var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_publishPosts, _deps_dropCache;
897
+ var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_storePagesRefusal, _deps_publishPosts, _deps_dropCache;
843
898
  const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
844
899
  const readNodes = (_deps_readNodes = deps.readNodes) != null ? _deps_readNodes : readAiDraftNodes;
845
900
  const publish = (_deps_publish = deps.publish) != null ? _deps_publish : aiPublishGuidedSite;
846
901
  const look = (_deps_look = deps.look) != null ? _deps_look : aiRunSiteLook;
847
902
  const contentRefusal = (_deps_contentRefusal = deps.contentRefusal) != null ? _deps_contentRefusal : aiSiteContentRefusal;
848
903
  const datasetRefusal = (_deps_datasetRefusal = deps.datasetRefusal) != null ? _deps_datasetRefusal : aiSiteDatasetRefusal;
904
+ const storePagesRefusal = (_deps_storePagesRefusal = deps.storePagesRefusal) != null ? _deps_storePagesRefusal : aiSiteStorePagesRefusal;
849
905
  const publishPosts = (_deps_publishPosts = deps.publishPosts) != null ? _deps_publishPosts : aiPublishSitePosts;
850
906
  const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
851
907
  return async (context)=>{
852
- var _ref, _job_outputs, _deps_posts, _deps_products, _deps_dataset, _job_outputs1;
853
- var _job_items, _rows_get, _rows_get1;
908
+ var _ref, _job_outputs, _deps_posts, _deps_products, _deps_dataset, _deps_storePages, _job_outputs1;
909
+ var _job_items, _rows_get, _rows_get1, _rows_get2;
854
910
  const { job } = context;
855
911
  // The scaffold asks no model of its own. What it names where it spends
856
912
  // nothing is the model its plan ran on, which is the one exchange this
@@ -903,6 +959,8 @@ export function createAiJobSiteStep(deps = {}) {
903
959
  now: context.now
904
960
  }, postsRow ? {
905
961
  blogUnwritten: !aiBuildItemDelivered(postsRow) || !((_postsRow_outputs = postsRow.outputs) == null ? void 0 : _postsRow_outputs.length)
962
+ } : {}, rows.get('store') ? {
963
+ unwrittenHrefs: unwrittenStoreHrefs(pages)
906
964
  } : {})).catch((error)=>{
907
965
  // The site is built either way; the pages stay drafts and say so.
908
966
  console.error('ai site publish threw', {
@@ -951,11 +1009,18 @@ export function createAiJobSiteStep(deps = {}) {
951
1009
  var _unit_screen;
952
1010
  return unit.kind === 'page' && ((_unit_screen = unit.screen) == null ? void 0 : _unit_screen.record);
953
1011
  }).map((unit)=>unit.slot));
1012
+ /** The paths of the store's pages the frame links that are not among the pages going live (AGL-3676). */ const unwrittenStoreHrefs = (pages)=>{
1013
+ const live = new Set(pages.map((page)=>{
1014
+ var _page_proposal;
1015
+ return (_page_proposal = page.proposal) == null ? void 0 : _page_proposal['storePage'];
1016
+ }).filter(Boolean));
1017
+ return aiStorePagesOfPlan(plan.screens).filter((page)=>!page.planned && !live.has(page.key)).map((page)=>page.href);
1018
+ };
954
1019
  const builtPages = (extra = [])=>{
955
1020
  var _job_outputs;
956
1021
  const ids = new Set(units.filter((unit)=>{
957
1022
  var _rows_get;
958
- return unit.kind === 'page' && !templates.has(unit.slot) && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
1023
+ return (unit.kind === 'page' || unit.kind === 'store') && !templates.has(unit.slot) && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
959
1024
  status: 'pending'
960
1025
  });
961
1026
  }).flatMap((unit)=>{
@@ -974,7 +1039,7 @@ export function createAiJobSiteStep(deps = {}) {
974
1039
  const catalog = unit.kind === 'products' ? runnerFor('products') : undefined;
975
1040
  const runner = unit.kind === 'theme' ? (lookContext)=>look(lookContext, lookContext.job) : unit.kind === 'posts' ? (_deps_posts = deps.posts) != null ? _deps_posts : runAiSitePostsUnit : unit.kind === 'products' ? (_deps_products = deps.products) != null ? _deps_products : catalog ? createAiSiteProductsRunner({
976
1041
  catalog
977
- }) : undefined : unit.kind === 'dataset' ? (_deps_dataset = deps.dataset) != null ? _deps_dataset : runAiSiteDatasetUnit : runnerFor(unit.jobKind);
1042
+ }) : undefined : unit.kind === 'dataset' ? (_deps_dataset = deps.dataset) != null ? _deps_dataset : runAiSiteDatasetUnit : unit.kind === 'store' ? (_deps_storePages = deps.storePages) != null ? _deps_storePages : runAiSiteStorePagesUnit : runnerFor(unit.jobKind);
978
1043
  if (!runner) return _extends({}, aiUnspentOutcome(model), init);
979
1044
  const othersOpen = ledgerUnits.some((one)=>{
980
1045
  var _rows_get;
@@ -1053,6 +1118,25 @@ export function createAiJobSiteStep(deps = {}) {
1053
1118
  note: `Not built: ${refusal}`
1054
1119
  }));
1055
1120
  }
1121
+ // The store's own pages ask the same (AGL-3676): a plan with a store, the
1122
+ // commerce plugin here, and a member who may edit the site's pages.
1123
+ if (unit.kind === 'store' && ((_rows_get2 = rows.get(unit.slot)) == null ? void 0 : _rows_get2.status) !== 'running') {
1124
+ const refusal = await storePagesRefusal(_extends({}, context, {
1125
+ job
1126
+ })).catch((error)=>{
1127
+ console.error('ai site store pages admission failed', {
1128
+ orgId: job.orgId,
1129
+ jobId: job.$id,
1130
+ error
1131
+ });
1132
+ return AI_SITE_UNIT_EMPTY_COPY;
1133
+ });
1134
+ if (refusal) return closing(settle({
1135
+ slot: unit.slot,
1136
+ status: 'skipped',
1137
+ note: `Not built: ${refusal}`
1138
+ }));
1139
+ }
1056
1140
  // A record template whose dataset was not made has nothing to show
1057
1141
  // (AGL-3616): it is not built, and the pages that list the dataset are
1058
1142
  // built without it.
@@ -1152,7 +1236,7 @@ export function createAiJobSiteStep(deps = {}) {
1152
1236
  outputs: outcome.outputs.map((output)=>output.id),
1153
1237
  // A store's products are listed before they are priced (AGL-3676):
1154
1238
  // the row says what is left before the store sells.
1155
- note: unit.kind === 'products' && outcome.outputs.length ? AI_SITE_PRODUCTS_PRICE_NOTE : note
1239
+ note: unit.kind === 'products' && outcome.outputs.length ? AI_SITE_PRODUCTS_PRICE_NOTE : unit.kind === 'store' && outcome.outputs.length ? AI_SITE_STORE_PAGES_NOTE : note
1156
1240
  }, degraded ? {
1157
1241
  degradedBy: degradation.degradedBy
1158
1242
  } : {}), spent);
@@ -1160,10 +1244,11 @@ export function createAiJobSiteStep(deps = {}) {
1160
1244
  rows.set(unit.slot, _extends({}, rows.get(unit.slot), {
1161
1245
  status: degraded ? 'degraded' : 'succeeded'
1162
1246
  }));
1163
- return finish(done, builtPages(unit.kind === 'page' && !templates.has(unit.slot) ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
1247
+ return finish(done, builtPages(unit.kind === 'page' && !templates.has(unit.slot) || unit.kind === 'store' ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
1164
1248
  };
1165
1249
  }
1166
1250
  export const runAiJobSiteStep = createAiJobSiteStep();
1251
+ /** ASSUMED: what the store pages' pass needs — five draft transactions and their search listings, no model. */ export const AI_SITE_STORE_PAGES_MINIMUM_MS = 15000;
1167
1252
  /**
1168
1253
  * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no
1169
1254
  * model itself: each pass hands one unit to the step registered for its kind,
@@ -1183,7 +1268,7 @@ export const runAiJobSiteStep = createAiJobSiteStep();
1183
1268
  const units = aiSiteJobUnits(plan, {
1184
1269
  welcomeEmail: inputs.welcomeEmail,
1185
1270
  content: aiSiteContentPart(job.inputs, false)
1186
- }).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
1271
+ }).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || unit.kind === 'store' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
1187
1272
  const outputs = (_job_outputs = job.outputs) != null ? _job_outputs : [];
1188
1273
  const ledger = ((_job_items1 = job.items) == null ? void 0 : _job_items1.length) ? job.items : aiSiteInitialLedger(units, outputs);
1189
1274
  const next = aiBuildNextUnit(aiSiteLedgerUnits(units), ledger);
@@ -1192,6 +1277,8 @@ export const runAiJobSiteStep = createAiJobSiteStep();
1192
1277
  if (unit.kind === 'theme') return AI_SITE_LOOK_BUDGET.minimumMs;
1193
1278
  if (unit.kind === 'posts') return AI_SITE_POST_BUDGET.minimumMs;
1194
1279
  if (unit.kind === 'dataset') return AI_SITE_DATASET_BUDGET.minimumMs;
1280
+ // The store's pages ask no model: five draft writes (AGL-3676).
1281
+ if (unit.kind === 'store') return AI_SITE_STORE_PAGES_MINIMUM_MS;
1195
1282
  return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiBuildBuiltRefs(aiSiteLedgerUnits(units), ledger, outputs)));
1196
1283
  }
1197
1284
  /**