@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +3 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-links.js +7 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +3 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.js +17 -9
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +6 -4
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +162 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-build-step.js +85 -16
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
- package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-language.js +13 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
- package/src/lib/jobs/ai-job-navigation-step.js +439 -0
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
- package/src/lib/jobs/ai-job-page-language.js +21 -5
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +1 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-plan-step.js +16 -7
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-products-step.js +2 -2
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
- package/src/lib/jobs/ai-job-settings-step.js +695 -0
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
- package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
- package/src/lib/jobs/ai-job-site-content.js +28 -12
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
- package/src/lib/jobs/ai-job-site-datasets.js +10 -3
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
- package/src/lib/jobs/ai-job-site-step.js +70 -10
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.d.ts +62 -4
- package/src/lib/jobs/ai-site-publish.js +203 -56
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
- package/src/lib/layout-language/ai-layout-compiler.js +26 -25
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
- package/src/lib/layout-language/ai-layout-frame.js +83 -13
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
- package/src/lib/layout-language/ai-layout-listings.js +34 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-tree.js +17 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +23 -0
- package/src/lib/model/ai-build-job.js +102 -13
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +25 -0
- package/src/lib/model/ai-build-plan.js +45 -1
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +52 -0
- package/src/lib/model/ai-credit-estimate.js +51 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +4 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -2
- package/src/lib/model/ai-jobs.types.js +4 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-layout-menus.d.ts +104 -0
- package/src/lib/model/ai-layout-menus.js +518 -0
- package/src/lib/model/ai-layout-menus.js.map +1 -0
- package/src/lib/model/ai-products.d.ts +31 -1
- package/src/lib/model/ai-products.js +34 -1
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +15 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +10 -0
- package/src/lib/model/ai-site-job.js +36 -9
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.js +1 -1
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +53 -0
- package/src/lib/runtime/ai-doctrine-validators.js +166 -25
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +7 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +1 -1
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +4 -2
- package/src/lib/runtime/ai-products-generation.js +12 -6
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +12 -0
- package/src/lib/runtime/site-inventory.js +29 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +37 -3
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
- package/src/lib/tools/ai-products-tool.d.ts +3 -1
- package/src/lib/tools/ai-products-tool.js +15 -5
- package/src/lib/tools/ai-products-tool.js.map +1 -1
|
@@ -26,6 +26,8 @@ import { renderEmailHtml } from "@aglyn/shared-util-email/email-render";
|
|
|
26
26
|
import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_BUILD_PLAN_LIMITS, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, AI_PLAN_DATASET_ITEM_OP, aiPlanCreateFor, aiPlanFormDatasetNames, aiPlanItemFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
|
|
27
27
|
import { aiCreationNoun, aiPlanCanPlaceForm, aiPlanUncreatable, aiPlanUncreatableKind } from "../model/ai-plan-capabilities.js";
|
|
28
28
|
import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome } from "../model/ai-site-job.js";
|
|
29
|
+
import { aiLayoutIsShopPage, aiLayoutShopGridSection } from "../layout-language/ai-layout-listings.js";
|
|
30
|
+
import { aiStorefrontSays } from "../layout-language/ai-layout-storefront.js";
|
|
29
31
|
import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
|
|
30
32
|
import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets } from "./ai-page-links.js";
|
|
31
33
|
import { AI_EMAIL_CLIP_BYTES, AI_OUTPUT_BUDGETS, AI_OUTPUT_SURFACE, AI_TEXT_LIMITS, isHeadlineVariant } from "./ai-palette.js";
|
|
@@ -3769,6 +3771,37 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
|
|
|
3769
3771
|
}))
|
|
3770
3772
|
});
|
|
3771
3773
|
}
|
|
3774
|
+
/**
|
|
3775
|
+
* A page section that is a layout region — a header, navigation or footer,
|
|
3776
|
+
* read by the name rule 2's `plan-layout-region-section` reads — is taken off
|
|
3777
|
+
* the page before the rules read the plan (AGL-3750). The region is already
|
|
3778
|
+
* the layout's, so the rule's own answer, "take the section off the page", is
|
|
3779
|
+
* the only one: a live Free start of 2026-10-12 (a bakery that sells cakes to
|
|
3780
|
+
* order) planned a Header and a Footer on its pages three times and stopped.
|
|
3781
|
+
*
|
|
3782
|
+
* Only a section that places nothing, or nothing but a layout (which rule 2
|
|
3783
|
+
* refuses placing as well), is taken off: one that places a form or a
|
|
3784
|
+
* component may be a band misnamed, so the rule still asks. A page whose
|
|
3785
|
+
* every section is a region keeps them all, and is re-asked as before, since
|
|
3786
|
+
* a page of no sections has no answer here. A home this leaves under its
|
|
3787
|
+
* fewest sections, or a Free plan still past its wall, is the existing
|
|
3788
|
+
* checks' to settle or refuse.
|
|
3789
|
+
*/ export function aiSettlePlanLayoutRegionSections(plan, inventory) {
|
|
3790
|
+
const kinds = inventoryKinds(inventory);
|
|
3791
|
+
const removable = (section)=>LAYOUT_REGION_NAME.test(section.name) && section.uses.every((ref)=>refKind(ref, plan, kinds) === 'layout');
|
|
3792
|
+
let changed = false;
|
|
3793
|
+
const screens = plan.screens.map((screen)=>{
|
|
3794
|
+
const kept = screen.sections.filter((section)=>!removable(section));
|
|
3795
|
+
if (kept.length === screen.sections.length || !kept.length) return screen;
|
|
3796
|
+
changed = true;
|
|
3797
|
+
return _extends({}, screen, {
|
|
3798
|
+
sections: kept
|
|
3799
|
+
});
|
|
3800
|
+
});
|
|
3801
|
+
return changed ? _extends({}, plan, {
|
|
3802
|
+
screens
|
|
3803
|
+
}) : plan;
|
|
3804
|
+
}
|
|
3772
3805
|
/** Every saved form a plan could place, the plan's own creations first. */ function planFormChoices(plan, inventory) {
|
|
3773
3806
|
var _ref;
|
|
3774
3807
|
return [
|
|
@@ -4419,6 +4452,135 @@ function slugKey(slug) {
|
|
|
4419
4452
|
const needs = !((_ref = inventory == null ? void 0 : inventory.layouts.length) != null ? _ref : 0) && capabilities.create.layout.allowed ? 1 : 0;
|
|
4420
4453
|
return Math.max(creates, needs);
|
|
4421
4454
|
}
|
|
4455
|
+
/** The Free wall a plan is held to, or null where the job is not on the Free taste. */ function freePlanWall(plan, inventory, capabilities) {
|
|
4456
|
+
if (!(capabilities == null ? void 0 : capabilities.freeTaste)) return null;
|
|
4457
|
+
const asked = plan.screens.reduce((sum, screen)=>sum + screen.sections.length, 0);
|
|
4458
|
+
const layouts = freePlanLayouts(plan, inventory, capabilities);
|
|
4459
|
+
const pages = Math.max(plan.screens.length, 1);
|
|
4460
|
+
// A Free site scaffold (AGL-3594) is held to its page cap first, and then to
|
|
4461
|
+
// what a SITE plan's worst case leaves for sections, with room for a retry.
|
|
4462
|
+
const sitePages = capabilities.freeSitePages;
|
|
4463
|
+
const overPages = sitePages !== undefined && plan.screens.length > sitePages;
|
|
4464
|
+
// A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
|
|
4465
|
+
const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
|
|
4466
|
+
const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
|
|
4467
|
+
layouts,
|
|
4468
|
+
pages,
|
|
4469
|
+
forms: Math.max(0, forms)
|
|
4470
|
+
}, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
|
|
4471
|
+
layouts,
|
|
4472
|
+
pages
|
|
4473
|
+
});
|
|
4474
|
+
// A site start's home keeps its full five (AGL-3660): told only "plan at
|
|
4475
|
+
// most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
|
|
4476
|
+
// its other page, so the redo names where the sections come from.
|
|
4477
|
+
const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
|
|
4478
|
+
pages,
|
|
4479
|
+
across: fits
|
|
4480
|
+
}) : 0;
|
|
4481
|
+
return {
|
|
4482
|
+
asked,
|
|
4483
|
+
fits,
|
|
4484
|
+
layouts,
|
|
4485
|
+
pages,
|
|
4486
|
+
sitePages,
|
|
4487
|
+
overPages,
|
|
4488
|
+
homeMin
|
|
4489
|
+
};
|
|
4490
|
+
}
|
|
4491
|
+
/**
|
|
4492
|
+
* The most sections past the Free wall a plan is trimmed by rather than
|
|
4493
|
+
* re-asked (AGL-3737). The live candle-shop start of 2026-10-12 planned 9
|
|
4494
|
+
* sections against a wall of 8 on all three answers, each with a home of 7:
|
|
4495
|
+
* the model reaching for the budget the site redo names ("plan 7 to 8") and
|
|
4496
|
+
* landing one band past it, which no re-ask cured. Within two, the plan's
|
|
4497
|
+
* shape is right and only a band or two the brief needs least is extra, so
|
|
4498
|
+
* taking those from the end loses nothing the plan depends on. Further over,
|
|
4499
|
+
* the shape itself is off — a list planned as sections, a page the wall
|
|
4500
|
+
* cannot pay for — and cutting more would drop content the model meant, so
|
|
4501
|
+
* that plan is still re-asked.
|
|
4502
|
+
*/ export const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
|
|
4503
|
+
/** A section through which a visitor reaches the owner: contact, a visit, a booking. */ const REACH_SECTION_NAME = /\b(?:contact|get in touch|visit(?: us)?|find us|locations?|hours|book(?:ing)?|reserve|reservations?)\b/i;
|
|
4504
|
+
/**
|
|
4505
|
+
* Whether the Free wall's trim keeps a section of a page (AGL-3737): the
|
|
4506
|
+
* page's opening, any section that places something (a dataset it lists, a
|
|
4507
|
+
* form, a component — dropping it would leave a creation unplaced), one that
|
|
4508
|
+
* collects answers or says how to reach the owner, and a storefront's
|
|
4509
|
+
* listing parts — the bestsellers, the collections, the sign-up, and a Shop
|
|
4510
|
+
* page's product grid.
|
|
4511
|
+
*/ function freeWallKeeps(screen, index) {
|
|
4512
|
+
const section = screen.sections[index];
|
|
4513
|
+
if (index === 0 || section.uses.length > 0) return true;
|
|
4514
|
+
if (FORM_SECTION_NAME.test(section.name) || REACH_SECTION_NAME.test(section.name)) return true;
|
|
4515
|
+
if (aiStorefrontSays('featured', section.name) || aiStorefrontSays('collections', section.name) || aiStorefrontSays('signup', section.name)) {
|
|
4516
|
+
return true;
|
|
4517
|
+
}
|
|
4518
|
+
return aiLayoutIsShopPage(screen) && aiLayoutShopGridSection(screen.sections) === index;
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* A Free plan a section or two past the wall is trimmed to fit before the
|
|
4522
|
+
* rules read the plan (AGL-3737), rather than re-asked: which sections go has
|
|
4523
|
+
* one answer, so the re-ask only costs the member a stopped start. The live
|
|
4524
|
+
* candle-shop start of 2026-10-12 asked for a home of 7 and a second page of
|
|
4525
|
+
* 2 against a wall of 8, three answers running, and stopped needing input.
|
|
4526
|
+
*
|
|
4527
|
+
* One section goes at a time, the last of its page that `freeWallKeeps`
|
|
4528
|
+
* does not hold, from the page furthest past its share: the home at / past
|
|
4529
|
+
* its fewest (`aiSiteHomeMinSections`, five on a site start whose wall pays
|
|
4530
|
+
* for a full home), any other page past two; on a tie, the longer page, then
|
|
4531
|
+
* the home. No home goes under its fewest and no page under one section. The
|
|
4532
|
+
* shares are what a site start is told to plan (a home of five or six, the
|
|
4533
|
+
* other page two or three): the candle shop's 7 + 2 becomes 6 + 2, never
|
|
4534
|
+
* 7 + 1, and the recorded yoga answer's 6 + 4 becomes 5 + 3, the shape its
|
|
4535
|
+
* own redo chose.
|
|
4536
|
+
*
|
|
4537
|
+
* A plan more than `AI_FREE_WALL_SETTLE_MAX_OVER` past the wall, a site
|
|
4538
|
+
* start past its page cap, a plan with a list split into sections, and a
|
|
4539
|
+
* plan the trim cannot fit without a section it keeps are returned
|
|
4540
|
+
* unchanged, for the wall to re-ask.
|
|
4541
|
+
*/ export function aiSettlePlanFreeWall(plan, inventory, capabilities = null) {
|
|
4542
|
+
const wall = freePlanWall(plan, inventory, capabilities);
|
|
4543
|
+
if (!wall || wall.overPages) return plan;
|
|
4544
|
+
const over = wall.asked - wall.fits;
|
|
4545
|
+
if (over <= 0 || over > AI_FREE_WALL_SETTLE_MAX_OVER) return plan;
|
|
4546
|
+
// A list split into sections is joined by its re-ask, which frees the room;
|
|
4547
|
+
// a trim would cut one of its items and hide the split from the rule.
|
|
4548
|
+
if (detectPlanSplitLists(plan, inventory, capabilities).length) return plan;
|
|
4549
|
+
const kept = plan.screens.map((screen)=>screen.sections.map((section, index)=>({
|
|
4550
|
+
section,
|
|
4551
|
+
index
|
|
4552
|
+
})));
|
|
4553
|
+
const home = plan.screens.findIndex(aiSitePlanIsHome);
|
|
4554
|
+
const floor = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 1;
|
|
4555
|
+
const target = (screenIndex)=>screenIndex === home ? Math.max(wall.homeMin, 1) : 2;
|
|
4556
|
+
// The last section of a page the trim may take, or -1.
|
|
4557
|
+
const last = (screenIndex)=>{
|
|
4558
|
+
const entries = kept[screenIndex];
|
|
4559
|
+
if (entries.length <= floor(screenIndex)) return -1;
|
|
4560
|
+
for(let at = entries.length - 1; at > 0; at -= 1){
|
|
4561
|
+
if (!freeWallKeeps(plan.screens[screenIndex], entries[at].index)) return at;
|
|
4562
|
+
}
|
|
4563
|
+
return -1;
|
|
4564
|
+
};
|
|
4565
|
+
for(let left = over; left > 0; left -= 1){
|
|
4566
|
+
let pick = -1;
|
|
4567
|
+
for(let index = 0; index < kept.length; index += 1){
|
|
4568
|
+
if (last(index) === -1) continue;
|
|
4569
|
+
const room = kept[index].length - target(index);
|
|
4570
|
+
const best = pick === -1 ? null : kept[pick].length - target(pick);
|
|
4571
|
+
if (best === null || room > best || room === best && (kept[index].length > kept[pick].length || kept[index].length === kept[pick].length && index === home)) {
|
|
4572
|
+
pick = index;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
if (pick === -1) return plan;
|
|
4576
|
+
kept[pick].splice(last(pick), 1);
|
|
4577
|
+
}
|
|
4578
|
+
return _extends({}, plan, {
|
|
4579
|
+
screens: plan.screens.map((screen, index)=>kept[index].length === screen.sections.length ? screen : _extends({}, screen, {
|
|
4580
|
+
sections: kept[index].map((entry)=>entry.section)
|
|
4581
|
+
}))
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4422
4584
|
/**
|
|
4423
4585
|
* The Free wall (plan): a plan asks for no more sections than the Free taste
|
|
4424
4586
|
* pays for at its worst beside what the job builds first (AGL-3070). A live
|
|
@@ -4429,14 +4591,10 @@ function slugKey(slug) {
|
|
|
4429
4591
|
* proven with, so no plan the proof admits is refused. It is no building rule,
|
|
4430
4592
|
* so it names none.
|
|
4431
4593
|
*/ export function detectPlanOverFreeWall(plan, inventory, capabilities) {
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
const layouts
|
|
4435
|
-
|
|
4436
|
-
// A Free site scaffold (AGL-3594) is held to its page cap first, and then to
|
|
4437
|
-
// what a SITE plan's worst case leaves for sections, with room for a retry.
|
|
4438
|
-
const sitePages = capabilities.freeSitePages;
|
|
4439
|
-
if (sitePages !== undefined && plan.screens.length > sitePages) {
|
|
4594
|
+
const wall = freePlanWall(plan, inventory, capabilities);
|
|
4595
|
+
if (!wall) return [];
|
|
4596
|
+
const { asked, layouts, pages, sitePages, fits, homeMin } = wall;
|
|
4597
|
+
if (sitePages !== undefined && wall.overPages) {
|
|
4440
4598
|
return [
|
|
4441
4599
|
{
|
|
4442
4600
|
rule: null,
|
|
@@ -4448,26 +4606,9 @@ function slugKey(slug) {
|
|
|
4448
4606
|
}
|
|
4449
4607
|
];
|
|
4450
4608
|
}
|
|
4451
|
-
// A site start's saved form is a step of its own (AGL-3596), counted on the wall since AGL-3660.
|
|
4452
|
-
const forms = plan.create.filter((entry)=>entry.kind === 'form').length - aiPlanUncreatable(plan, capabilities).filter((entry)=>entry.kind === 'form').length;
|
|
4453
|
-
const fits = sitePages !== undefined ? aiFreeSiteSectionsWithin({
|
|
4454
|
-
layouts,
|
|
4455
|
-
pages,
|
|
4456
|
-
forms: Math.max(0, forms)
|
|
4457
|
-
}, FREE_AI_TASTE_CREDITS_PER_MONTH) : aiFreePageSectionsWithin({
|
|
4458
|
-
layouts,
|
|
4459
|
-
pages
|
|
4460
|
-
});
|
|
4461
4609
|
if (asked <= fits) return [];
|
|
4462
4610
|
const job = pages > 1 ? `a Free plan of ${pages} pages` : 'a Free page';
|
|
4463
4611
|
const beside = layouts ? ` that creates ${layouts > 1 ? `${layouts} layouts` : 'its layout'}` : '';
|
|
4464
|
-
// A site start's home keeps its full five (AGL-3660): told only "plan at
|
|
4465
|
-
// most 8", the live yoga start of 2026-10-08 cut a home of 6 to 4 and kept
|
|
4466
|
-
// its other page, so the redo names where the sections come from.
|
|
4467
|
-
const homeMin = sitePages !== undefined && plan.screens.some(aiSitePlanIsHome) ? aiSiteHomeMinSections({
|
|
4468
|
-
pages,
|
|
4469
|
-
across: fits
|
|
4470
|
-
}) : 0;
|
|
4471
4612
|
const keepHome = homeMin ? ` Keep the home page at / at ${homeMin} or more sections, and take the rest from the other ${pages > 2 ? 'pages' : 'page'}.` : '';
|
|
4472
4613
|
// A site start's redo keeps the plan full (AGL-3660): the wall's sections
|
|
4473
4614
|
// are its budget to use, and a redo told only "at most" cuts far under it.
|