@aglyn/plugins-ai 1.0.0-beta.269 → 1.0.0-beta.271
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.d.ts +1 -1
- package/src/lib/activity/ai-activity-actions.js +1 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-credits-card.component.d.ts +8 -6
- package/src/lib/components/ai-credits-card.component.js +39 -22
- package/src/lib/components/ai-credits-card.component.js.map +1 -1
- package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
- package/src/lib/components/ai-job-input-form.component.js +100 -0
- package/src/lib/components/ai-job-input-form.component.js.map +1 -0
- package/src/lib/components/ai-job-links.d.ts +6 -0
- package/src/lib/components/ai-job-links.js +9 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +4 -0
- package/src/lib/components/ai-job-requests.js +4 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.d.ts +6 -0
- package/src/lib/components/ai-products-hub-card.component.js +21 -6
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +3 -2
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +99 -31
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
- package/src/lib/components/ai-site-start-steps.component.js +651 -15
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
- package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
- package/src/lib/components/ai-site-start-tracking.js +14 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -1
- package/src/lib/components/ai-site-style-previews.js +26 -22
- package/src/lib/components/ai-site-style-previews.js.map +1 -1
- package/src/lib/components/ai-store-finish-card.component.d.ts +14 -3
- package/src/lib/components/ai-store-finish-card.component.js +67 -9
- package/src/lib/components/ai-store-finish-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
- package/src/lib/components/ai-upsell-dialog.component.js +8 -6
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.js +14 -3
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +9 -1
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +8 -4
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +4 -7
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/declarations.d.ts +2 -2
- package/src/lib/declarations.js +9 -5
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +3 -1
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-build-step.js +13 -5
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +24 -1
- package/src/lib/jobs/ai-job-form-step.js +93 -7
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +35 -11
- package/src/lib/jobs/ai-job-page-language.js +68 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +17 -0
- package/src/lib/jobs/ai-job-page-step.js +40 -17
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
- package/src/lib/jobs/ai-job-plan-step.js +103 -29
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.js +4 -3
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
- package/src/lib/jobs/ai-job-site-datasets.js +140 -14
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-look.js +18 -5
- package/src/lib/jobs/ai-job-site-look.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
- package/src/lib/jobs/ai-job-site-step.js +95 -22
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +8 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
- package/src/lib/jobs/ai-job-tracking-step.js +28 -5
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +8 -1
- package/src/lib/jobs/ai-jobs.js +63 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.d.ts +57 -0
- package/src/lib/jobs/ai-layout-owner-photos.js +110 -0
- package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +62 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +523 -72
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
- package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
- package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
- package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
- package/src/lib/jobs/ai-site-inspiration.js +346 -0
- package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +26 -1
- package/src/lib/jobs/ai-site-publish.js +162 -13
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +16 -0
- package/src/lib/jobs/ai-theme-brand-inputs.js +18 -4
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js +291 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
- package/src/lib/layout-language/ai-layout-compiler.js +438 -25
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
- package/src/lib/layout-language/ai-layout-contact.js +137 -0
- package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-copy.js +34 -2
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
- package/src/lib/layout-language/ai-layout-design.js +411 -97
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +23 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-labels.js +12 -6
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
- package/src/lib/layout-language/ai-layout-language.js +4 -3
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
- package/src/lib/layout-language/ai-layout-links.js +30 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
- package/src/lib/layout-language/ai-layout-pictures.js +145 -22
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +16 -1
- package/src/lib/model/ai-build-job.js +40 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-job-input.d.ts +33 -0
- package/src/lib/model/ai-job-input.js +59 -0
- package/src/lib/model/ai-job-input.js.map +1 -0
- package/src/lib/model/ai-job-notice.d.ts +4 -0
- package/src/lib/model/ai-job-notice.js +11 -1
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +40 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-details.d.ts +122 -0
- package/src/lib/model/ai-site-details.js +300 -0
- package/src/lib/model/ai-site-details.js.map +1 -0
- package/src/lib/model/ai-site-entity.d.ts +181 -0
- package/src/lib/model/ai-site-entity.js +390 -0
- package/src/lib/model/ai-site-entity.js.map +1 -0
- package/src/lib/model/ai-site-inspiration.d.ts +72 -0
- package/src/lib/model/ai-site-inspiration.js +109 -0
- package/src/lib/model/ai-site-inspiration.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +13 -1
- package/src/lib/model/ai-site-job.js +15 -2
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +109 -21
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +10 -0
- package/src/lib/model/ai-site-look.js +24 -0
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +19 -5
- package/src/lib/model/ai-site-start-seo.js +25 -7
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start-steps.d.ts +9 -3
- package/src/lib/model/ai-site-start-steps.js +67 -5
- package/src/lib/model/ai-site-start-steps.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +68 -4
- package/src/lib/model/ai-site-start.js +166 -13
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-upsell-copy.js +4 -1
- package/src/lib/model/ai-upsell-copy.js.map +1 -1
- package/src/lib/native-nav.d.ts +26 -0
- package/src/lib/native-nav.js +58 -0
- package/src/lib/native-nav.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +4 -3
- package/src/lib/plan-entitlements.js +3 -8
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
- package/src/lib/runtime/ai-dataset-generation.js +12 -2
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
- package/src/lib/runtime/ai-doctrine-validators.js +54 -44
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +1 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +7 -0
- package/src/lib/runtime/ai-node-tree.js +9 -1
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/server/ai-create-route.d.ts +1 -1
- package/src/lib/server/ai-create-route.js +3 -4
- package/src/lib/server/ai-create-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +27 -3
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +7 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/usage/assist-credits.d.ts +4 -4
- package/src/lib/usage/assist-credits.js +4 -4
- package/src/lib/usage/assist-credits.js.map +1 -1
- package/src/lib/usage/staff-org-ai-view.js +6 -3
- package/src/lib/usage/staff-org-ai-view.js.map +1 -1
- package/src/lib/usage/staff-org-ai.d.ts +4 -2
- package/src/lib/usage/staff-org-ai.js +7 -7
- package/src/lib/usage/staff-org-ai.js.map +1 -1
- package/src/lib/usage/staff-org-usage-ai.js +2 -2
- package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-inspiration.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 type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isServerReleaseFlagOnForOrg } from '@aglyn/tenant-data-admin/server/release-flags'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n AI_SITE_INSPIRATION_FLAG,\n aiSiteInspirationLimit,\n parseAiSiteInspiration,\n} from '../model/ai-site-inspiration'\nimport { AI_SITE_FONT_PAIRINGS, type AiSiteKind } from '../model/ai-site-kinds'\nimport { aiHexOklch, type AiSiteLookPins } from '../model/ai-site-look'\nimport {\n aiSiteInspirationSignals,\n type AiSiteInspiration,\n type AiSiteInspirationFeature,\n type AiSiteInspirationFontMood,\n type AiSiteInspirationSection,\n type AiSiteInspirationSite,\n} from './ai-site-inspiration-signals'\nimport { AI_THEME_BRAND_BUDGET_MS, readReferencePage, type AiReferencePage } from './ai-theme-brand-inputs'\n\n/**\n * Sites I like, as the site job uses them (AGL-3975): read once, by the plan\n * step, into signals (`ai-site-inspiration-signals.ts`) the plan keeps; then\n *\n * - the PLAN is told each site's STRUCTURE — its sections in order, its\n * spacing and its header — and, on a paid plan, the FEATURES it offers\n * mapped onto Aglyn's own parts, in one fenced block labeled as data;\n * - the LOOK unit PINS what the sites' STYLE says — brand colors, the type's\n * mood, buttons, cards, corners, the header, spacing, motion — so those\n * dimensions are kept whatever the model or the seed would choose, and\n * every other dimension is chosen as it always was. That is what keeps the\n * site Aglyn's own: the platform's base themes, its font pairings and its\n * WCAG-held colors, arranged the way the person liked.\n *\n * Nothing of a site but those values travels: no word, picture, name or logo.\n */\n\n/**\n * The wall clock every inspiration site together may take: the theme job's\n * brand budget, the same bound for the same reads (one page and two sheets\n * a site). A site start's plan has the room: it is held to its own outline\n * ceiling, far under the `job.plan` row the step's least time is sized to\n * (`ai-site-inspiration.spec.ts` holds the sum).\n */\nexport const AI_SITE_INSPIRATION_BUDGET_MS = AI_THEME_BRAND_BUDGET_MS\n\n/** The unit input the look reads the plan's signals from. */\nexport const AI_SITE_INSPIRATION_SIGNALS_INPUT = 'inspirationSignals'\n\n// ── Reading ──────────────────────────────────────────────────────────────\n\n/** The read that leaves the process, replaceable where a spec reads recorded pages. */\nexport type AiSiteInspirationPageReader = (url: URL, signal: AbortSignal) => Promise<AiReferencePage | null>\n\n/** One abort for the caller's signal and a clock, whichever ends first. */\nfunction deadline(signal: AbortSignal | undefined, ms: number) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ms)\n const onAbort = () => controller.abort()\n if (signal?.aborted) controller.abort()\n signal?.addEventListener('abort', onAbort)\n return {\n signal: controller.signal,\n done: () => {\n clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n },\n }\n}\n\n/**\n * Each site's signals, in the order the person gave them, read together\n * under {@link AI_SITE_INSPIRATION_BUDGET_MS}. A site that cannot be read is\n * counted and left out; `null` when none could be. Features are kept only on\n * a paid plan (`features: false` empties them).\n */\nexport async function readAiSiteInspirationSites(\n urls: readonly string[],\n options: { signal?: AbortSignal; features: boolean; readPage?: AiSiteInspirationPageReader; budgetMs?: number },\n): Promise<AiSiteInspiration | null> {\n if (!urls.length) return null\n const read = options.readPage ?? readReferencePage\n const clock = deadline(options.signal, options.budgetMs ?? AI_SITE_INSPIRATION_BUDGET_MS)\n try {\n const pages = await Promise.all(\n urls.map(async (url) => {\n try {\n return await read(new URL(url), clock.signal)\n } catch {\n return null\n }\n }),\n )\n const sites = pages.flatMap((page): AiSiteInspirationSite[] => {\n if (!page) return []\n const site = aiSiteInspirationSignals(page)\n return [options.features ? site : { ...site, features: [] }]\n })\n return sites.length ? { v: 1, sites, unread: urls.length - sites.length } : null\n } finally {\n clock.done()\n }\n}\n\n/** What a site start's inspiration is read with: specs and the eval recorder hand in their own. */\nexport type AiSiteInspirationReader = (input: {\n job: Pick<AiJob, 'kind' | 'orgId' | 'inputs'>\n org: Partial<AglynOrgBilling> | null\n freeTaste: boolean\n signal?: AbortSignal\n}) => Promise<AiSiteInspiration | null>\n\n/** Whether the release flag is on for a workspace; one that cannot be read is off. */\nexport async function aiSiteInspirationReleased(orgId: string): Promise<boolean> {\n return isServerReleaseFlagOnForOrg(AI_SITE_INSPIRATION_FLAG, orgId).catch(() => false)\n}\n\n/**\n * The reader the plan step uses: a site job's sites, held to its plan's\n * limit, read only while the release flag is on for its workspace. Anything\n * else — another kind, no sites, the flag off — reads nothing and fetches\n * nothing, so the plan is asked exactly what it was before.\n */\nexport function createAiSiteInspirationReader(\n deps: { released?: (orgId: string) => Promise<boolean>; readPage?: AiSiteInspirationPageReader } = {},\n): AiSiteInspirationReader {\n const released = deps.released ?? aiSiteInspirationReleased\n return async ({ job, freeTaste, signal }) => {\n if (job.kind !== 'site') return null\n const urls = parseAiSiteInspiration(job.inputs)\n if (typeof urls === 'string' || !urls.length) return null\n if (!(await released(job.orgId))) return null\n return readAiSiteInspirationSites(urls.slice(0, aiSiteInspirationLimit(freeTaste)), {\n features: !freeTaste,\n ...(signal ? { signal } : {}),\n ...(deps.readPage ? { readPage: deps.readPage } : {}),\n })\n }\n}\n\nexport const readAiJobSiteInspiration: AiSiteInspirationReader = createAiSiteInspirationReader()\n\n// ── The plan ─────────────────────────────────────────────────────────────\n\n/** How the plan is told each kind of section, in the plan's own words. */\nexport const AI_SITE_INSPIRATION_SECTION_WORDS: Readonly<Record<AiSiteInspirationSection, string>> = {\n hero: 'hero',\n about: 'about or story',\n features: 'services or features',\n steps: 'how it works',\n stats: 'numbers',\n gallery: 'gallery',\n menu: 'menu',\n products: 'products',\n pricing: 'pricing',\n testimonials: 'testimonials',\n logos: 'clients or partners',\n team: 'team',\n events: 'events',\n blog: 'latest posts',\n faq: 'questions and answers',\n newsletter: 'newsletter sign-up',\n booking: 'booking',\n contact: 'contact',\n cta: 'call to action',\n}\n\n/**\n * Each feature a site offers, as the part of Aglyn a paid start builds for\n * it: only what a site start really makes or places (a section the plan\n * names, the site's own form, the commerce Product grid on a Shop page, the\n * blog's posts, the events a start lists), never an integration it lacks.\n * A part this start cannot build is never offered\n * ({@link aiSiteInspirationBuildable}).\n */\nexport const AI_SITE_INSPIRATION_FEATURE_PARTS: Readonly<Record<AiSiteInspirationFeature, string>> = {\n booking: 'a booking section that places the site’s form, for visitors to ask for a time',\n store: 'a Shop page with a Product grid section',\n blog: 'a section of latest posts on the home page',\n forms: 'the contact form on the page a visitor opens to get in touch',\n gallery: 'a gallery section of the work',\n events: 'an upcoming events section',\n menu: 'a menu section, the dishes as a ruled list',\n testimonials: 'a testimonials section, only where the site has customer words of its own',\n newsletter: 'a newsletter sign-up section',\n}\n\nconst DENSITY_WORDS = { compact: 'compact, many short sections', regular: 'regular, even room between sections', airy: 'airy, generous room around each section' } as const\nconst HEADER_WORDS = { start: 'logo at the start, links after it', center: 'logo centered' } as const\n\n/** The fence the plan reads the signals in: labeled as data, closed by name. */\nconst FENCE_OPEN = '<inspiration-structure>'\nconst FENCE_CLOSE = '</inspiration-structure>'\n\n/**\n * What a site start can build of the features a site offers (AGL-3975). Some\n * parts exist only on some starts: the Product grid lists a store's catalog,\n * the newsletter sign-up is the commerce plugin's and runs on a store that\n * sells, the latest posts are a blog's first posts, an events list is the\n * events a start drafts for an event-led business, and a testimonials\n * section needs customer words the site has. Told any of them elsewhere, the\n * live eval's restaurant plan (2026-10-19) was asked for an events list and a\n * newsletter sign-up no restaurant start can place.\n */\nexport interface AiSiteInspirationStart {\n /** The kind of site the start makes (`AI_SITE_KINDS`), when it names one. */\n siteKind: string | null\n /** The start writes first posts: a blog (`aiSiteContentPart` says `posts`). */\n posts: boolean\n /** The start sells: a paid store's first products (`aiSiteContentPart` says `products`). */\n sells: boolean\n /** The start may draft its first events (AGL-3755): an event-led business the events gate admits. */\n events: boolean\n /** The site has customer words of its own (`aiPlanHasCustomerWords`). */\n customerWords: boolean\n}\n\n/** Whether this start can build the part a feature maps to. */\nexport function aiSiteInspirationBuildable(feature: AiSiteInspirationFeature, start: AiSiteInspirationStart): boolean {\n switch (feature) {\n case 'store':\n return start.siteKind === 'store'\n case 'newsletter':\n return start.sells\n case 'blog':\n return start.posts\n case 'events':\n return start.events\n case 'testimonials':\n return start.customerWords\n default:\n return true\n }\n}\n\n/** The kinds of section that are a part only some starts build, by that part. */\nconst AI_SITE_INSPIRATION_SECTION_PARTS: Partial<Record<AiSiteInspirationSection, AiSiteInspirationFeature>> = {\n products: 'store',\n newsletter: 'newsletter',\n blog: 'blog',\n events: 'events',\n testimonials: 'testimonials',\n}\n\n/** A site's sections in order, less the kinds this start cannot build: the order a plan can follow. */\nexport function aiSiteInspirationOrder(site: Pick<AiSiteInspirationSite, 'sections'>, start: AiSiteInspirationStart): AiSiteInspirationSection[] {\n return site.sections.filter((kind) => {\n const part = AI_SITE_INSPIRATION_SECTION_PARTS[kind]\n return !part || aiSiteInspirationBuildable(part, start)\n })\n}\n\n/**\n * The order a start's home is planned in (AGL-3975): the first site's, as the\n * person named it first and as the look breaks a tie, less what this start\n * cannot build; `null` where no site gave one. Where there is one it takes\n * the place of the home arc the job's seed would choose (AGL-3899): told\n * both, the live eval's plans of 2026-10-19 kept the seeded arc every time,\n * since the arc said \"Keep that order\" and the block yields to every rule\n * above it.\n */\nexport function aiSiteInspirationHomeOrder(\n inspiration: AiSiteInspiration | null,\n start: AiSiteInspirationStart,\n): { site: number; sections: AiSiteInspirationSection[] } | null {\n const sites = inspiration?.sites ?? []\n for (const [index, site] of sites.entries()) {\n const sections = aiSiteInspirationOrder(site, start)\n if (sections.length) return { site: index, sections }\n }\n return null\n}\n\n/**\n * What a site plan is told of the sites the person likes: one fenced block of\n * signals, every value from this module's own vocabulary, with the\n * features only on a paid plan, and only those this start can build. Empty\n * for none.\n */\nexport function aiSiteInspirationPlanLines(\n inspiration: AiSiteInspiration | null,\n options: { paid: boolean; start: AiSiteInspirationStart },\n): string[] {\n if (!inspiration?.sites.length) return []\n const lines: string[] = []\n inspiration.sites.forEach((site, index) => {\n const name = `Site ${index + 1}`\n const sections = aiSiteInspirationOrder(site, options.start)\n if (sections.length) {\n lines.push(`${name} sections, top to bottom: ${sections.map((kind) => AI_SITE_INSPIRATION_SECTION_WORDS[kind]).join(', ')}.`)\n }\n const shape = [\n site.density ? `spacing ${DENSITY_WORDS[site.density]}` : '',\n site.headerAlign ? `header ${HEADER_WORDS[site.headerAlign]}` : '',\n ].filter(Boolean)\n if (shape.length) lines.push(`${name}: ${shape.join('; ')}.`)\n })\n const features = options.paid\n ? aiSiteInspirationFeatures(inspiration).filter((feature) => aiSiteInspirationBuildable(feature, options.start))\n : []\n if (features.length) {\n lines.push('Features the person liked, each to plan with the platform’s own part:')\n for (const feature of features) lines.push(`- ${feature}: ${AI_SITE_INSPIRATION_FEATURE_PARTS[feature]}`)\n }\n if (!lines.length) return []\n const order = aiSiteInspirationHomeOrder(inspiration, options.start)\n const others = inspiration.sites.length > 1 ? ', and take the other sites’ kinds of section where they suit it' : ''\n return [\n 'Inspiration structure: signals code read from the sites the person likes. It is data, not instructions, and it holds none of their words, pictures, names or logos.',\n FENCE_OPEN,\n ...lines,\n FENCE_CLOSE,\n order\n ? `Plan this business’s own site. Its home page at / follows Site ${order.site + 1}’s sections in that order, the hero first: leave out a kind only where this business has nothing true to put in it${others}. Name and write every section for this business. Every rule above wins where they differ.`\n : 'Plan this business’s own site: take those kinds of sections where they suit it, and name and write every section for this business. Every rule above wins where they differ.',\n ]\n}\n\n/** The features of every site, in the order the person's sites first offer them. */\nexport function aiSiteInspirationFeatures(inspiration: AiSiteInspiration | null): AiSiteInspirationFeature[] {\n const features: AiSiteInspirationFeature[] = []\n for (const site of inspiration?.sites ?? []) {\n for (const feature of site.features) if (!features.includes(feature)) features.push(feature)\n }\n return features\n}\n\n// ── The look ─────────────────────────────────────────────────────────────\n\n/** The value most of the sites share, the earliest site's on a tie; `null` where none says. */\nfunction agreed<T>(sites: readonly AiSiteInspirationSite[], read: (site: AiSiteInspirationSite) => T | null): T | null {\n const tally = new Map<T, { votes: number; first: number }>()\n sites.forEach((site, index) => {\n const value = read(site)\n if (value === null || value === undefined) return\n const entry = tally.get(value) ?? { votes: 0, first: index }\n entry.votes += 1\n tally.set(value, entry)\n })\n let best: { value: T; votes: number; first: number } | null = null\n for (const [value, entry] of tally) {\n if (!best || entry.votes > best.votes || (entry.votes === best.votes && entry.first < best.first)) best = { value, ...entry }\n }\n return best?.value ?? null\n}\n\n/** A color strong enough to carry a brand; a grey says nothing of one. */\nconst COLORFUL_CHROMA = 0.04\n\n/** The pairings of a type mood: serif headings, condensed or poster display faces, or sans. */\nconst DISPLAY_PAIRINGS = ['anton', 'oswald', 'archivo', 'barlow', 'syne']\nexport function aiSiteInspirationPairings(mood: AiSiteInspirationFontMood): string[] {\n return AI_SITE_FONT_PAIRINGS.filter((pairing) =>\n mood === 'serif'\n ? pairing.heading.category === 'serif'\n : mood === 'display'\n ? DISPLAY_PAIRINGS.includes(pairing.id)\n : pairing.heading.category === 'sans-serif' && !DISPLAY_PAIRINGS.includes(pairing.id),\n ).map((pairing) => pairing.id)\n}\n\n/**\n * The look dimensions the sites pin (AGL-3975): what most of them agree on,\n * the first site's where they split, mapped onto the look's closed sets. The\n * type is pinned to its MOOD, and the pairing is the job's seed's among\n * Aglyn's pairings of that mood — the kind's own first — so it is never their\n * font. The brand color is the first site's strongest color; a brand the\n * person gave wins over it in the look itself.\n */\nexport function aiSiteInspirationLookPins(\n inspiration: AiSiteInspiration | null,\n input: { kind: Pick<AiSiteKind, 'look'>; seed: number },\n): AiSiteLookPins {\n const sites = inspiration?.sites ?? []\n if (!sites.length) return {}\n const pins: AiSiteLookPins = {}\n const colors = (sites.find((site) => site.colors.length)?.colors ?? []).filter((hex) => aiHexOklch(hex).C >= COLORFUL_CHROMA)\n if (colors[0]) {\n pins.brand = colors[0]\n pins.hue = Math.round(aiHexOklch(colors[0]).h) % 360\n }\n if (colors[1]) pins.accent = Math.round(aiHexOklch(colors[1]).h) % 360\n const ground = agreed(sites, (site) => site.ground)\n if (ground) pins.ground = ground\n const mood = agreed(sites, (site) => site.fontMood)\n if (mood) {\n const all = aiSiteInspirationPairings(mood)\n const own = all.filter((id) => input.kind.look.fonts.includes(id))\n const choices = own.length ? own : all\n const fonts = choices[(input.seed >>> 0) % choices.length]\n if (fonts) pins.fonts = fonts\n }\n const corners = agreed(sites, (site) => site.corners)\n if (corners) pins.corners = corners\n const buttons = agreed(sites, (site) => site.buttons)\n if (buttons) pins.buttons = buttons\n const cards = agreed(sites, (site) => site.cards)\n if (cards) pins.cards = cards\n const header = agreed(sites, (site) => site.header)\n if (header) pins.header = header\n const headerAlign = agreed(sites, (site) => site.headerAlign)\n if (headerAlign) pins.headerAlign = headerAlign\n const density = agreed(sites, (site) => site.density)\n if (density) pins.density = density\n const motion = agreed(sites, (site) => site.motion)\n if (motion) pins.motion = motion\n return pins\n}\n"],"names":["isServerReleaseFlagOnForOrg","AI_SITE_INSPIRATION_FLAG","aiSiteInspirationLimit","parseAiSiteInspiration","AI_SITE_FONT_PAIRINGS","aiHexOklch","aiSiteInspirationSignals","AI_THEME_BRAND_BUDGET_MS","readReferencePage","AI_SITE_INSPIRATION_BUDGET_MS","AI_SITE_INSPIRATION_SIGNALS_INPUT","deadline","signal","ms","controller","AbortController","timer","setTimeout","abort","onAbort","aborted","addEventListener","done","clearTimeout","removeEventListener","readAiSiteInspirationSites","urls","options","length","read","readPage","clock","budgetMs","pages","Promise","all","map","url","URL","sites","flatMap","page","site","features","v","unread","aiSiteInspirationReleased","orgId","catch","createAiSiteInspirationReader","deps","released","job","freeTaste","kind","inputs","slice","readAiJobSiteInspiration","AI_SITE_INSPIRATION_SECTION_WORDS","hero","about","steps","stats","gallery","menu","products","pricing","testimonials","logos","team","events","blog","faq","newsletter","booking","contact","cta","AI_SITE_INSPIRATION_FEATURE_PARTS","store","forms","DENSITY_WORDS","compact","regular","airy","HEADER_WORDS","start","center","FENCE_OPEN","FENCE_CLOSE","aiSiteInspirationBuildable","feature","siteKind","sells","posts","customerWords","AI_SITE_INSPIRATION_SECTION_PARTS","aiSiteInspirationOrder","sections","filter","part","aiSiteInspirationHomeOrder","inspiration","index","entries","aiSiteInspirationPlanLines","lines","forEach","name","push","join","shape","density","headerAlign","Boolean","paid","aiSiteInspirationFeatures","order","others","includes","agreed","tally","Map","value","undefined","entry","get","votes","first","set","best","COLORFUL_CHROMA","DISPLAY_PAIRINGS","aiSiteInspirationPairings","mood","pairing","heading","category","id","aiSiteInspirationLookPins","input","pins","colors","find","hex","C","brand","hue","Math","round","h","accent","ground","fontMood","own","look","fonts","choices","seed","corners","buttons","cards","header","motion"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,2BAA2B,QAAQ,gDAA+C;AAE3F,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,sBAAsB,QACjB,kCAA8B;AACrC,SAASC,qBAAqB,QAAyB,4BAAwB;AAC/E,SAASC,UAAU,QAA6B,2BAAuB;AACvE,SACEC,wBAAwB,QAMnB,mCAA+B;AACtC,SAASC,wBAAwB,EAAEC,iBAAiB,QAA8B,6BAAyB;AAE3G;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,OAAO,MAAMC,gCAAgCF,yBAAwB;AAErE,2DAA2D,GAC3D,OAAO,MAAMG,oCAAoC,qBAAoB;AAOrE,yEAAyE,GACzE,SAASC,SAASC,MAA+B,EAAEC,EAAU;IAC3D,MAAMC,aAAa,IAAIC;IACvB,MAAMC,QAAQC,WAAW,IAAMH,WAAWI,KAAK,IAAIL;IACnD,MAAMM,UAAU,IAAML,WAAWI,KAAK;IACtC,IAAIN,0BAAAA,OAAQQ,OAAO,EAAEN,WAAWI,KAAK;IACrCN,0BAAAA,OAAQS,gBAAgB,CAAC,SAASF;IAClC,OAAO;QACLP,QAAQE,WAAWF,MAAM;QACzBU,MAAM;YACJC,aAAaP;YACbJ,0BAAAA,OAAQY,mBAAmB,CAAC,SAASL;QACvC;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeM,2BACpBC,IAAuB,EACvBC,OAA+G;QAGlGA,mBAC0BA;IAFvC,IAAI,CAACD,KAAKE,MAAM,EAAE,OAAO;IACzB,MAAMC,QAAOF,oBAAAA,QAAQG,QAAQ,YAAhBH,oBAAoBnB;IACjC,MAAMuB,QAAQpB,SAASgB,QAAQf,MAAM,GAAEe,oBAAAA,QAAQK,QAAQ,YAAhBL,oBAAoBlB;IAC3D,IAAI;QACF,MAAMwB,QAAQ,MAAMC,QAAQC,GAAG,CAC7BT,KAAKU,GAAG,CAAC,OAAOC;YACd,IAAI;gBACF,OAAO,MAAMR,KAAK,IAAIS,IAAID,MAAMN,MAAMnB,MAAM;YAC9C,EAAE,eAAM;gBACN,OAAO;YACT;QACF;QAEF,MAAM2B,QAAQN,MAAMO,OAAO,CAAC,CAACC;YAC3B,IAAI,CAACA,MAAM,OAAO,EAAE;YACpB,MAAMC,OAAOpC,yBAAyBmC;YACtC,OAAO;gBAACd,QAAQgB,QAAQ,GAAGD,OAAO,aAAKA;oBAAMC,UAAU,EAAE;;aAAG;QAC9D;QACA,OAAOJ,MAAMX,MAAM,GAAG;YAAEgB,GAAG;YAAGL;YAAOM,QAAQnB,KAAKE,MAAM,GAAGW,MAAMX,MAAM;QAAC,IAAI;IAC9E,SAAU;QACRG,MAAMT,IAAI;IACZ;AACF;AAUA,oFAAoF,GACpF,OAAO,eAAewB,0BAA0BC,KAAa;IAC3D,OAAO/C,4BAA4BC,0BAA0B8C,OAAOC,KAAK,CAAC,IAAM;AAClF;AAEA;;;;;CAKC,GACD,OAAO,SAASC,8BACdC,OAAmG,CAAC,CAAC;QAEpFA;IAAjB,MAAMC,YAAWD,iBAAAA,KAAKC,QAAQ,YAAbD,iBAAiBJ;IAClC,OAAO,OAAO,EAAEM,GAAG,EAAEC,SAAS,EAAEzC,MAAM,EAAE;QACtC,IAAIwC,IAAIE,IAAI,KAAK,QAAQ,OAAO;QAChC,MAAM5B,OAAOvB,uBAAuBiD,IAAIG,MAAM;QAC9C,IAAI,OAAO7B,SAAS,YAAY,CAACA,KAAKE,MAAM,EAAE,OAAO;QACrD,IAAI,CAAE,MAAMuB,SAASC,IAAIL,KAAK,GAAI,OAAO;QACzC,OAAOtB,2BAA2BC,KAAK8B,KAAK,CAAC,GAAGtD,uBAAuBmD,aAAa;YAClFV,UAAU,CAACU;WACPzC,SAAS;YAAEA;QAAO,IAAI,CAAC,GACvBsC,KAAKpB,QAAQ,GAAG;YAAEA,UAAUoB,KAAKpB,QAAQ;QAAC,IAAI,CAAC;IAEvD;AACF;AAEA,OAAO,MAAM2B,2BAAoDR,gCAA+B;AAEhG,4EAA4E;AAE5E,wEAAwE,GACxE,OAAO,MAAMS,oCAAwF;IACnGC,MAAM;IACNC,OAAO;IACPjB,UAAU;IACVkB,OAAO;IACPC,OAAO;IACPC,SAAS;IACTC,MAAM;IACNC,UAAU;IACVC,SAAS;IACTC,cAAc;IACdC,OAAO;IACPC,MAAM;IACNC,QAAQ;IACRC,MAAM;IACNC,KAAK;IACLC,YAAY;IACZC,SAAS;IACTC,SAAS;IACTC,KAAK;AACP,EAAC;AAED;;;;;;;CAOC,GACD,OAAO,MAAMC,oCAAwF;IACnGH,SAAS;IACTI,OAAO;IACPP,MAAM;IACNQ,OAAO;IACPhB,SAAS;IACTO,QAAQ;IACRN,MAAM;IACNG,cAAc;IACdM,YAAY;AACd,EAAC;AAED,MAAMO,gBAAgB;IAAEC,SAAS;IAAgCC,SAAS;IAAuCC,MAAM;AAA0C;AACjK,MAAMC,eAAe;IAAEC,OAAO;IAAqCC,QAAQ;AAAgB;AAE3F,8EAA8E,GAC9E,MAAMC,aAAa;AACnB,MAAMC,cAAc;AAyBpB,6DAA6D,GAC7D,OAAO,SAASC,2BAA2BC,OAAiC,EAAEL,KAA6B;IACzG,OAAQK;QACN,KAAK;YACH,OAAOL,MAAMM,QAAQ,KAAK;QAC5B,KAAK;YACH,OAAON,MAAMO,KAAK;QACpB,KAAK;YACH,OAAOP,MAAMQ,KAAK;QACpB,KAAK;YACH,OAAOR,MAAMf,MAAM;QACrB,KAAK;YACH,OAAOe,MAAMS,aAAa;QAC5B;YACE,OAAO;IACX;AACF;AAEA,+EAA+E,GAC/E,MAAMC,oCAAyG;IAC7G9B,UAAU;IACVQ,YAAY;IACZF,MAAM;IACND,QAAQ;IACRH,cAAc;AAChB;AAEA,qGAAqG,GACrG,OAAO,SAAS6B,uBAAuBtD,IAA6C,EAAE2C,KAA6B;IACjH,OAAO3C,KAAKuD,QAAQ,CAACC,MAAM,CAAC,CAAC5C;QAC3B,MAAM6C,OAAOJ,iCAAiC,CAACzC,KAAK;QACpD,OAAO,CAAC6C,QAAQV,2BAA2BU,MAAMd;IACnD;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASe,2BACdC,WAAqC,EACrChB,KAA6B;;IAE7B,MAAM9C,gBAAQ8D,+BAAAA,YAAa9D,KAAK,mBAAI,EAAE;IACtC,KAAK,MAAM,CAAC+D,OAAO5D,KAAK,IAAIH,MAAMgE,OAAO,GAAI;QAC3C,MAAMN,WAAWD,uBAAuBtD,MAAM2C;QAC9C,IAAIY,SAASrE,MAAM,EAAE,OAAO;YAAEc,MAAM4D;YAAOL;QAAS;IACtD;IACA,OAAO;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASO,2BACdH,WAAqC,EACrC1E,OAAyD;IAEzD,IAAI,EAAC0E,+BAAAA,YAAa9D,KAAK,CAACX,MAAM,GAAE,OAAO,EAAE;IACzC,MAAM6E,QAAkB,EAAE;IAC1BJ,YAAY9D,KAAK,CAACmE,OAAO,CAAC,CAAChE,MAAM4D;QAC/B,MAAMK,OAAO,CAAC,KAAK,EAAEL,QAAQ,GAAG;QAChC,MAAML,WAAWD,uBAAuBtD,MAAMf,QAAQ0D,KAAK;QAC3D,IAAIY,SAASrE,MAAM,EAAE;YACnB6E,MAAMG,IAAI,CAAC,GAAGD,KAAK,0BAA0B,EAAEV,SAAS7D,GAAG,CAAC,CAACkB,OAASI,iCAAiC,CAACJ,KAAK,EAAEuD,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9H;QACA,MAAMC,QAAQ;YACZpE,KAAKqE,OAAO,GAAG,CAAC,QAAQ,EAAE/B,aAAa,CAACtC,KAAKqE,OAAO,CAAC,EAAE,GAAG;YAC1DrE,KAAKsE,WAAW,GAAG,CAAC,OAAO,EAAE5B,YAAY,CAAC1C,KAAKsE,WAAW,CAAC,EAAE,GAAG;SACjE,CAACd,MAAM,CAACe;QACT,IAAIH,MAAMlF,MAAM,EAAE6E,MAAMG,IAAI,CAAC,GAAGD,KAAK,EAAE,EAAEG,MAAMD,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9D;IACA,MAAMlE,WAAWhB,QAAQuF,IAAI,GACzBC,0BAA0Bd,aAAaH,MAAM,CAAC,CAACR,UAAYD,2BAA2BC,SAAS/D,QAAQ0D,KAAK,KAC5G,EAAE;IACN,IAAI1C,SAASf,MAAM,EAAE;QACnB6E,MAAMG,IAAI,CAAC;QACX,KAAK,MAAMlB,WAAW/C,SAAU8D,MAAMG,IAAI,CAAC,CAAC,EAAE,EAAElB,QAAQ,EAAE,EAAEb,iCAAiC,CAACa,QAAQ,EAAE;IAC1G;IACA,IAAI,CAACe,MAAM7E,MAAM,EAAE,OAAO,EAAE;IAC5B,MAAMwF,QAAQhB,2BAA2BC,aAAa1E,QAAQ0D,KAAK;IACnE,MAAMgC,SAAShB,YAAY9D,KAAK,CAACX,MAAM,GAAG,IAAI,oEAAoE;IAClH,OAAO;QACL;QACA2D;WACGkB;QACHjB;QACA4B,QACI,CAAC,+DAA+D,EAAEA,MAAM1E,IAAI,GAAG,EAAE,kHAAkH,EAAE2E,OAAO,0FAA0F,CAAC,GACvS;KACL;AACH;AAEA,kFAAkF,GAClF,OAAO,SAASF,0BAA0Bd,WAAqC;;IAC7E,MAAM1D,WAAuC,EAAE;IAC/C,KAAK,MAAMD,gBAAQ2D,+BAAAA,YAAa9D,KAAK,mBAAI,EAAE,CAAE;QAC3C,KAAK,MAAMmD,WAAWhD,KAAKC,QAAQ,CAAE,IAAI,CAACA,SAAS2E,QAAQ,CAAC5B,UAAU/C,SAASiE,IAAI,CAAClB;IACtF;IACA,OAAO/C;AACT;AAEA,4EAA4E;AAE5E,6FAA6F,GAC7F,SAAS4E,OAAUhF,KAAuC,EAAEV,IAA+C;;IACzG,MAAM2F,QAAQ,IAAIC;IAClBlF,MAAMmE,OAAO,CAAC,CAAChE,MAAM4D;YAGLkB;QAFd,MAAME,QAAQ7F,KAAKa;QACnB,IAAIgF,UAAU,QAAQA,UAAUC,WAAW;QAC3C,MAAMC,SAAQJ,aAAAA,MAAMK,GAAG,CAACH,kBAAVF,aAAoB;YAAEM,OAAO;YAAGC,OAAOzB;QAAM;QAC3DsB,MAAME,KAAK,IAAI;QACfN,MAAMQ,GAAG,CAACN,OAAOE;IACnB;IACA,IAAIK,OAA0D;IAC9D,KAAK,MAAM,CAACP,OAAOE,MAAM,IAAIJ,MAAO;QAClC,IAAI,CAACS,QAAQL,MAAME,KAAK,GAAGG,KAAKH,KAAK,IAAKF,MAAME,KAAK,KAAKG,KAAKH,KAAK,IAAIF,MAAMG,KAAK,GAAGE,KAAKF,KAAK,EAAGE,OAAO;YAAEP;WAAUE;IACxH;IACA,eAAOK,wBAAAA,KAAMP,KAAK,mBAAI;AACxB;AAEA,wEAAwE,GACxE,MAAMQ,kBAAkB;AAExB,6FAA6F,GAC7F,MAAMC,mBAAmB;IAAC;IAAS;IAAU;IAAW;IAAU;CAAO;AACzE,OAAO,SAASC,0BAA0BC,IAA+B;IACvE,OAAOjI,sBAAsB8F,MAAM,CAAC,CAACoC,UACnCD,SAAS,UACLC,QAAQC,OAAO,CAACC,QAAQ,KAAK,UAC7BH,SAAS,YACPF,iBAAiBb,QAAQ,CAACgB,QAAQG,EAAE,IACpCH,QAAQC,OAAO,CAACC,QAAQ,KAAK,gBAAgB,CAACL,iBAAiBb,QAAQ,CAACgB,QAAQG,EAAE,GACxFrG,GAAG,CAAC,CAACkG,UAAYA,QAAQG,EAAE;AAC/B;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC,0BACdrC,WAAqC,EACrCsC,KAAuD;;QAKvCpG;IAHhB,MAAMA,gBAAQ8D,+BAAAA,YAAa9D,KAAK,mBAAI,EAAE;IACtC,IAAI,CAACA,MAAMX,MAAM,EAAE,OAAO,CAAC;IAC3B,MAAMgH,OAAuB,CAAC;IAC9B,MAAMC,SAAS,WAACtG,cAAAA,MAAMuG,IAAI,CAAC,CAACpG,OAASA,KAAKmG,MAAM,CAACjH,MAAM,sBAAvCW,YAA0CsG,MAAM,oBAAI,EAAE,EAAE3C,MAAM,CAAC,CAAC6C,MAAQ1I,WAAW0I,KAAKC,CAAC,IAAId;IAC7G,IAAIW,MAAM,CAAC,EAAE,EAAE;QACbD,KAAKK,KAAK,GAAGJ,MAAM,CAAC,EAAE;QACtBD,KAAKM,GAAG,GAAGC,KAAKC,KAAK,CAAC/I,WAAWwI,MAAM,CAAC,EAAE,EAAEQ,CAAC,IAAI;IACnD;IACA,IAAIR,MAAM,CAAC,EAAE,EAAED,KAAKU,MAAM,GAAGH,KAAKC,KAAK,CAAC/I,WAAWwI,MAAM,CAAC,EAAE,EAAEQ,CAAC,IAAI;IACnE,MAAME,SAAShC,OAAOhF,OAAO,CAACG,OAASA,KAAK6G,MAAM;IAClD,IAAIA,QAAQX,KAAKW,MAAM,GAAGA;IAC1B,MAAMlB,OAAOd,OAAOhF,OAAO,CAACG,OAASA,KAAK8G,QAAQ;IAClD,IAAInB,MAAM;QACR,MAAMlG,MAAMiG,0BAA0BC;QACtC,MAAMoB,MAAMtH,IAAI+D,MAAM,CAAC,CAACuC,KAAOE,MAAMrF,IAAI,CAACoG,IAAI,CAACC,KAAK,CAACrC,QAAQ,CAACmB;QAC9D,MAAMmB,UAAUH,IAAI7H,MAAM,GAAG6H,MAAMtH;QACnC,MAAMwH,QAAQC,OAAO,CAAC,AAACjB,CAAAA,MAAMkB,IAAI,KAAK,CAAA,IAAKD,QAAQhI,MAAM,CAAC;QAC1D,IAAI+H,OAAOf,KAAKe,KAAK,GAAGA;IAC1B;IACA,MAAMG,UAAUvC,OAAOhF,OAAO,CAACG,OAASA,KAAKoH,OAAO;IACpD,IAAIA,SAASlB,KAAKkB,OAAO,GAAGA;IAC5B,MAAMC,UAAUxC,OAAOhF,OAAO,CAACG,OAASA,KAAKqH,OAAO;IACpD,IAAIA,SAASnB,KAAKmB,OAAO,GAAGA;IAC5B,MAAMC,QAAQzC,OAAOhF,OAAO,CAACG,OAASA,KAAKsH,KAAK;IAChD,IAAIA,OAAOpB,KAAKoB,KAAK,GAAGA;IACxB,MAAMC,SAAS1C,OAAOhF,OAAO,CAACG,OAASA,KAAKuH,MAAM;IAClD,IAAIA,QAAQrB,KAAKqB,MAAM,GAAGA;IAC1B,MAAMjD,cAAcO,OAAOhF,OAAO,CAACG,OAASA,KAAKsE,WAAW;IAC5D,IAAIA,aAAa4B,KAAK5B,WAAW,GAAGA;IACpC,MAAMD,UAAUQ,OAAOhF,OAAO,CAACG,OAASA,KAAKqE,OAAO;IACpD,IAAIA,SAAS6B,KAAK7B,OAAO,GAAGA;IAC5B,MAAMmD,SAAS3C,OAAOhF,OAAO,CAACG,OAASA,KAAKwH,MAAM;IAClD,IAAIA,QAAQtB,KAAKsB,MAAM,GAAGA;IAC1B,OAAOtB;AACT"}
|
|
@@ -132,7 +132,19 @@ export declare function aiLayoutWithNavigation(nodes: NodesMap, input: {
|
|
|
132
132
|
homeId?: string | null;
|
|
133
133
|
/** Paths whose links come out: a blog the start linked and never wrote (AGL-3660). */
|
|
134
134
|
droppedHrefs?: readonly string[];
|
|
135
|
+
/** Pages whose links come out: a page the start planned and did not put live (AGL-3988). */
|
|
136
|
+
droppedScreenIds?: readonly string[];
|
|
135
137
|
}): NodesMap | null;
|
|
138
|
+
/**
|
|
139
|
+
* A page's nodes with every link to a page the start did not put live taken
|
|
140
|
+
* out (AGL-3988), and every frame that held only such links with them, up to
|
|
141
|
+
* its section: a published site never links to a 404. The beta.270 Sugar
|
|
142
|
+
* Hollow home page linked its refused Galleries page from "See the
|
|
143
|
+
* galleries" and the dataset band's "View all galleries". A link is a Page
|
|
144
|
+
* Link to one of `screenIds`; anything else that opens one (a picture) keeps
|
|
145
|
+
* its place and loses the link. `null` when nothing links one. Pure.
|
|
146
|
+
*/
|
|
147
|
+
export declare function aiPageWithoutLinksTo(nodes: NodesMap, screenIds: readonly string[]): NodesMap | null;
|
|
136
148
|
/** The blog a start writes, as its header entry links it (`aiSiteBlogNavPage`). */
|
|
137
149
|
export interface AiSiteBlogEntry {
|
|
138
150
|
label: string;
|
|
@@ -180,13 +192,26 @@ export declare function aiMergeSitePublish(previous: AiJobSitePublish, retried:
|
|
|
180
192
|
* draft, for the same reason, and is logged.
|
|
181
193
|
*/
|
|
182
194
|
export declare function aiPublishGuidedSite(firestore: Firestore, input: {
|
|
183
|
-
job: Pick<AiJob, '$id' | 'orgId' | 'hostId'
|
|
195
|
+
job: Pick<AiJob, '$id' | 'orgId' | 'hostId'> & Partial<Pick<AiJob, 'inputs'>>;
|
|
184
196
|
outputs: readonly AiJobOutput[];
|
|
185
197
|
now: Date;
|
|
186
198
|
/** The start owed its blog's posts and wrote none (AGL-3660): its links to the blog come out. */
|
|
187
199
|
blogUnwritten?: boolean;
|
|
188
200
|
/** Paths the layout links that the start did not write, such as a store page (AGL-3676): their links come out. */
|
|
189
201
|
unwrittenHrefs?: readonly string[];
|
|
202
|
+
/**
|
|
203
|
+
* The pages the start planned and did not build (AGL-3988): a page its
|
|
204
|
+
* checks refused. Every link to one — the header's, the footer's, a
|
|
205
|
+
* button's on a page going live — comes out, and so does every link to
|
|
206
|
+
* a page this publish leaves a draft.
|
|
207
|
+
*/
|
|
208
|
+
unbuiltScreenIds?: readonly string[];
|
|
209
|
+
/**
|
|
210
|
+
* The start's pages in the order the site lists them (AGL-3988): a page
|
|
211
|
+
* going live with no place of its own in Pages takes its place there, so
|
|
212
|
+
* the 404 page's links follow the site's order, never the alphabet.
|
|
213
|
+
*/
|
|
214
|
+
siteOrder?: readonly string[];
|
|
190
215
|
/** A store's links a build drafted into the site's own layout (AGL-3676), which go live with its pages. */
|
|
191
216
|
storeLinks?: AiSitePublishStoreLinks | null;
|
|
192
217
|
/**
|
|
@@ -25,9 +25,12 @@ import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measur
|
|
|
25
25
|
import { FieldValue } from "firebase-admin/firestore";
|
|
26
26
|
import { aiLayoutFrameHollows, aiLayoutFrameHollowsSaid, aiLayoutFrameIsHollow, aiLayoutRepairFrameHollows, aiLayoutWithoutFrameHollows } from "../layout-language/ai-layout-frame-hollows.js";
|
|
27
27
|
import { AI_SITE_BLOG_SLUGS } from "../model/ai-site-job.js";
|
|
28
|
+
import { aiSiteEntityUpdates, aiSiteEntityValues, aiSiteListingUpdates } from "../model/ai-site-entity.js";
|
|
29
|
+
import { aiSiteSeoProposalForInputs } from "../model/ai-site-start-seo.js";
|
|
28
30
|
import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
|
|
29
31
|
import { aiLayoutWithMenuChanges, aiNoMenuChanges } from "../model/ai-layout-menus.js";
|
|
30
32
|
import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-job-store-links-layout.js";
|
|
33
|
+
import { updateAiDraftNodes } from "./ai-job-drafts.js";
|
|
31
34
|
/**
|
|
32
35
|
* Whether a build puts its pages live (AGL-3616): only where its request
|
|
33
36
|
* asked to (`publish`, set by Assist's proposal) AND the member ticked the
|
|
@@ -118,7 +121,7 @@ function navEntriesOf(outputs) {
|
|
|
118
121
|
* starter's Toolbar. `null` when nothing changes, or when the layout has no
|
|
119
122
|
* header to hold an entry and no link to repoint or take out.
|
|
120
123
|
*/ export function aiLayoutWithNavigation(nodes, input) {
|
|
121
|
-
var _input_droppedHrefs;
|
|
124
|
+
var _input_droppedHrefs, _input_droppedScreenIds;
|
|
122
125
|
const map = {};
|
|
123
126
|
for (const [id, node] of Object.entries(nodes)){
|
|
124
127
|
map[id] = _extends({}, node, node.props ? {
|
|
@@ -132,13 +135,16 @@ function navEntriesOf(outputs) {
|
|
|
132
135
|
let changed = false;
|
|
133
136
|
// The header, the phone menu and the footer link a blog by its path before
|
|
134
137
|
// its posts are written (AGL-3660); a start whose posts were not written
|
|
135
|
-
// publishes none of those links to a page that does not exist.
|
|
138
|
+
// publishes none of those links to a page that does not exist. Nor does a
|
|
139
|
+
// start link a page it planned and did not put live (AGL-3988): the
|
|
140
|
+
// beta.270 Sugar Hollow header linked its refused Galleries page.
|
|
136
141
|
const dropped = new Set((_input_droppedHrefs = input.droppedHrefs) != null ? _input_droppedHrefs : []);
|
|
137
|
-
|
|
142
|
+
const unbuilt = new Set((_input_droppedScreenIds = input.droppedScreenIds) != null ? _input_droppedScreenIds : []);
|
|
143
|
+
if (dropped.size || unbuilt.size) {
|
|
138
144
|
const gone = new Set(Object.entries(map).filter(([, node])=>{
|
|
139
|
-
var _ref;
|
|
140
|
-
var _node_props;
|
|
141
|
-
return node.componentId === 'muiScreenLink' && dropped.has(String((_ref = (_node_props = node.props) == null ? void 0 : _node_props['href']) != null ? _ref : ''));
|
|
145
|
+
var _ref, _ref1;
|
|
146
|
+
var _node_props, _node_props1;
|
|
147
|
+
return node.componentId === 'muiScreenLink' && (dropped.has(String((_ref = (_node_props = node.props) == null ? void 0 : _node_props['href']) != null ? _ref : '')) || unbuilt.has(String((_ref1 = (_node_props1 = node.props) == null ? void 0 : _node_props1['screenId']) != null ? _ref1 : '')));
|
|
142
148
|
}).map(([id])=>id));
|
|
143
149
|
if (gone.size) {
|
|
144
150
|
for (const id of gone)delete map[id];
|
|
@@ -191,6 +197,88 @@ function navEntriesOf(outputs) {
|
|
|
191
197
|
}
|
|
192
198
|
return changed ? map : null;
|
|
193
199
|
}
|
|
200
|
+
/** Elements that only frame what they hold, which go once a link taken out leaves them nothing. */ const LINK_FRAMES = new Set([
|
|
201
|
+
'muiStack',
|
|
202
|
+
'muiBox',
|
|
203
|
+
'muiGrid',
|
|
204
|
+
'muiButtonGroup',
|
|
205
|
+
'muiCardActions'
|
|
206
|
+
]);
|
|
207
|
+
/**
|
|
208
|
+
* A page's nodes with every link to a page the start did not put live taken
|
|
209
|
+
* out (AGL-3988), and every frame that held only such links with them, up to
|
|
210
|
+
* its section: a published site never links to a 404. The beta.270 Sugar
|
|
211
|
+
* Hollow home page linked its refused Galleries page from "See the
|
|
212
|
+
* galleries" and the dataset band's "View all galleries". A link is a Page
|
|
213
|
+
* Link to one of `screenIds`; anything else that opens one (a picture) keeps
|
|
214
|
+
* its place and loses the link. `null` when nothing links one. Pure.
|
|
215
|
+
*/ export function aiPageWithoutLinksTo(nodes, screenIds) {
|
|
216
|
+
const missing = new Set(screenIds.filter(Boolean));
|
|
217
|
+
if (!missing.size) return null;
|
|
218
|
+
const map = {};
|
|
219
|
+
for (const [id, node] of Object.entries(nodes)){
|
|
220
|
+
map[id] = _extends({}, node, node.props ? {
|
|
221
|
+
props: _extends({}, node.props)
|
|
222
|
+
} : {}, node.nodes ? {
|
|
223
|
+
nodes: [
|
|
224
|
+
...node.nodes
|
|
225
|
+
]
|
|
226
|
+
} : {});
|
|
227
|
+
}
|
|
228
|
+
const opensMissing = (node)=>{
|
|
229
|
+
var _ref;
|
|
230
|
+
var _node_props;
|
|
231
|
+
return missing.has(String((_ref = node == null ? void 0 : (_node_props = node.props) == null ? void 0 : _node_props['screenId']) != null ? _ref : ''));
|
|
232
|
+
};
|
|
233
|
+
const ids = Object.keys(map).filter((id)=>opensMissing(map[id]));
|
|
234
|
+
if (!ids.length) return null;
|
|
235
|
+
const parentOf = (id)=>{
|
|
236
|
+
var _Object_keys_find;
|
|
237
|
+
return (_Object_keys_find = Object.keys(map).find((key)=>{
|
|
238
|
+
var _map_key_nodes, _map_key;
|
|
239
|
+
return (_map_key = map[key]) == null ? void 0 : (_map_key_nodes = _map_key.nodes) == null ? void 0 : _map_key_nodes.includes(id);
|
|
240
|
+
})) != null ? _Object_keys_find : null;
|
|
241
|
+
};
|
|
242
|
+
const removeTree = (id)=>{
|
|
243
|
+
var _ref;
|
|
244
|
+
var _map_id;
|
|
245
|
+
for (const child of [
|
|
246
|
+
...(_ref = (_map_id = map[id]) == null ? void 0 : _map_id.nodes) != null ? _ref : []
|
|
247
|
+
])removeTree(child);
|
|
248
|
+
delete map[id];
|
|
249
|
+
};
|
|
250
|
+
for (const id of ids){
|
|
251
|
+
var _ref, _ref1;
|
|
252
|
+
var _map_parent, _map_parent1, _map_parent2;
|
|
253
|
+
const node = map[id];
|
|
254
|
+
if (!node) continue;
|
|
255
|
+
if (node.componentId !== 'muiScreenLink') {
|
|
256
|
+
var _node_props;
|
|
257
|
+
const props = _extends({}, (_node_props = node.props) != null ? _node_props : {});
|
|
258
|
+
delete props['screenId'];
|
|
259
|
+
node.props = props;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
let parent = parentOf(id);
|
|
263
|
+
if (parent) map[parent] = _extends({}, map[parent], {
|
|
264
|
+
nodes: ((_ref = (_map_parent = map[parent]) == null ? void 0 : _map_parent.nodes) != null ? _ref : []).filter((child)=>child !== id)
|
|
265
|
+
});
|
|
266
|
+
removeTree(id);
|
|
267
|
+
// The frames it leaves empty, never a section nor the page's root.
|
|
268
|
+
while(parent && map[parent] && !((_ref1 = (_map_parent1 = map[parent]) == null ? void 0 : _map_parent1.nodes) != null ? _ref1 : []).length && LINK_FRAMES.has(String((_map_parent2 = map[parent]) == null ? void 0 : _map_parent2.componentId))){
|
|
269
|
+
var _ref2;
|
|
270
|
+
var _map_above;
|
|
271
|
+
const above = parentOf(parent);
|
|
272
|
+
if (!above) break;
|
|
273
|
+
map[above] = _extends({}, map[above], {
|
|
274
|
+
nodes: ((_ref2 = (_map_above = map[above]) == null ? void 0 : _map_above.nodes) != null ? _ref2 : []).filter((child)=>child !== parent)
|
|
275
|
+
});
|
|
276
|
+
delete map[parent];
|
|
277
|
+
parent = above;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return map;
|
|
281
|
+
}
|
|
194
282
|
/**
|
|
195
283
|
* The layout's nodes with the blog's header entry put back (AGL-3747): a
|
|
196
284
|
* start whose posts part failed published with every link to the blog taken
|
|
@@ -325,7 +413,7 @@ function navEntriesOf(outputs) {
|
|
|
325
413
|
* Firestore failure of the publish write itself reports every page as a
|
|
326
414
|
* draft, for the same reason, and is logged.
|
|
327
415
|
*/ export async function aiPublishGuidedSite(firestore, input, deps = {}) {
|
|
328
|
-
var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1;
|
|
416
|
+
var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _input_siteOrder, _input_unbuiltScreenIds, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1, _input_language, _input_language1;
|
|
329
417
|
const sendSitePublished = (_deps_sendSitePublished = deps.sendSitePublished) != null ? _deps_sendSitePublished : sendGa4SitePublished;
|
|
330
418
|
const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
|
|
331
419
|
const { job, now } = input;
|
|
@@ -352,6 +440,7 @@ function navEntriesOf(outputs) {
|
|
|
352
440
|
const routing = _extends({}, before);
|
|
353
441
|
const entries = {};
|
|
354
442
|
const accepted = [];
|
|
443
|
+
const siteOrder = (_input_siteOrder = input.siteOrder) != null ? _input_siteOrder : [];
|
|
355
444
|
for (const page of pages){
|
|
356
445
|
var _ref1, _snapshot_get, _snapshot_get1;
|
|
357
446
|
const snapshot = await hostRef.collection('screens').doc(page.id).get();
|
|
@@ -384,14 +473,49 @@ function navEntriesOf(outputs) {
|
|
|
384
473
|
}
|
|
385
474
|
routing[page.id] = slug;
|
|
386
475
|
entries[page.id] = slug;
|
|
387
|
-
|
|
476
|
+
// Its place in Pages, where the owner has not given it one (AGL-3988).
|
|
477
|
+
const place = siteOrder.indexOf(page.id);
|
|
478
|
+
const placed = typeof snapshot.get('order') === 'number';
|
|
479
|
+
accepted.push(_extends({
|
|
388
480
|
id: page.id,
|
|
389
481
|
label,
|
|
390
482
|
path: slug,
|
|
391
483
|
slug
|
|
392
|
-
}
|
|
484
|
+
}, place >= 0 && !placed ? {
|
|
485
|
+
order: place
|
|
486
|
+
} : {}));
|
|
393
487
|
}
|
|
394
488
|
if (!accepted.length) return result;
|
|
489
|
+
// A page that is not live once this publish is done — planned and never
|
|
490
|
+
// built, or left a draft just now — is linked from nowhere (AGL-3988).
|
|
491
|
+
const live = new Set([
|
|
492
|
+
...accepted.map((page)=>page.id),
|
|
493
|
+
...Object.keys(before)
|
|
494
|
+
]);
|
|
495
|
+
const notLive = [
|
|
496
|
+
...new Set([
|
|
497
|
+
...(_input_unbuiltScreenIds = input.unbuiltScreenIds) != null ? _input_unbuiltScreenIds : [],
|
|
498
|
+
...result.drafts.map((page)=>page.id)
|
|
499
|
+
])
|
|
500
|
+
].filter((id)=>id && !live.has(id));
|
|
501
|
+
if (notLive.length) {
|
|
502
|
+
for (const page of accepted){
|
|
503
|
+
await updateAiDraftNodes(firestore, {
|
|
504
|
+
kind: 'screen',
|
|
505
|
+
hostId,
|
|
506
|
+
id: page.id,
|
|
507
|
+
now,
|
|
508
|
+
update: (nodes)=>aiPageWithoutLinksTo(nodes, notLive)
|
|
509
|
+
}).catch((error)=>{
|
|
510
|
+
console.error('ai site publish: links to an unbuilt page not taken out', {
|
|
511
|
+
hostId,
|
|
512
|
+
jobId: job.$id,
|
|
513
|
+
pageId: page.id,
|
|
514
|
+
error
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
395
519
|
// The first real home page replaces the placeholder in the same write.
|
|
396
520
|
const home = (_accepted_find = accepted.find((page)=>page.path === SCREEN_ROOT_PATH)) != null ? _accepted_find : null;
|
|
397
521
|
const releases = home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH ? placeholder : null;
|
|
@@ -407,6 +531,7 @@ function navEntriesOf(outputs) {
|
|
|
407
531
|
...aiSiteUnwrittenBlogHrefs(input.blogUnwritten),
|
|
408
532
|
...(_input_unwrittenHrefs = input.unwrittenHrefs) != null ? _input_unwrittenHrefs : []
|
|
409
533
|
],
|
|
534
|
+
droppedScreenIds: notLive,
|
|
410
535
|
retiredHomeId: releases,
|
|
411
536
|
homeId: (_ref = home == null ? void 0 : home.id) != null ? _ref : null,
|
|
412
537
|
jobId: job.$id,
|
|
@@ -474,13 +599,37 @@ function navEntriesOf(outputs) {
|
|
|
474
599
|
defaultLocale: host.get('defaultLocale')
|
|
475
600
|
}, input.language);
|
|
476
601
|
if (language) Object.assign(hostUpdates, language);
|
|
477
|
-
|
|
602
|
+
// Who publishes the site, from the start's answers (AGL-3979): its Setup ->
|
|
603
|
+
// SEO -> Entity and AI agents fields and its Business details, each only
|
|
604
|
+
// where empty, so every page's structured data and the site's /llms.txt
|
|
605
|
+
// name its business type, phone, email, address, hours, areas, profiles and
|
|
606
|
+
// logo. Every page carries the entity, so the announce asks for the whole
|
|
607
|
+
// site when any of it changes.
|
|
608
|
+
const siteName = typeof host.get('displayName') === 'string' ? host.get('displayName') : null;
|
|
609
|
+
const entity = aiSiteEntityUpdates(host.get('seo'), aiSiteEntityValues(job.inputs, {
|
|
610
|
+
language: (_input_language = input.language) != null ? _input_language : null,
|
|
611
|
+
siteName
|
|
612
|
+
}), {
|
|
613
|
+
logoUrl: host.get('logoUrl'),
|
|
614
|
+
business: host.get('business')
|
|
615
|
+
});
|
|
616
|
+
Object.assign(hostUpdates, entity);
|
|
617
|
+
// The site's own listing (AGL-3979): the start's, with the site's name,
|
|
618
|
+
// over the title and description its creation seeded and nowhere else. A
|
|
619
|
+
// listing the owner changed keeps their words, and the SEO section still
|
|
620
|
+
// offers the start's beside them. Every page falls back to it, and
|
|
621
|
+
// `og:site_name` and /llms.txt read it, so it asks for the whole site too.
|
|
622
|
+
const listing = aiSiteListingUpdates(host.get('seo'), siteName, aiSiteSeoProposalForInputs(job.inputs, (_input_language1 = input.language) != null ? _input_language1 : null, siteName));
|
|
623
|
+
Object.assign(hostUpdates, listing);
|
|
624
|
+
const entireHost = layoutWrites.length > 0 || language !== null || Object.keys(entity).length > 0 || Object.keys(listing).length > 0;
|
|
478
625
|
batch.update(hostRef, hostUpdates);
|
|
479
626
|
for (const page of accepted){
|
|
480
|
-
batch.set(hostRef.collection('screens').doc(page.id), {
|
|
627
|
+
batch.set(hostRef.collection('screens').doc(page.id), _extends({
|
|
481
628
|
slug: page.slug,
|
|
482
629
|
publishedAt: now
|
|
483
|
-
}, {
|
|
630
|
+
}, page.order !== undefined ? {
|
|
631
|
+
order: page.order
|
|
632
|
+
} : {}), {
|
|
484
633
|
merge: true
|
|
485
634
|
});
|
|
486
635
|
}
|
|
@@ -553,7 +702,7 @@ function navEntriesOf(outputs) {
|
|
|
553
702
|
* `null` when there is nothing to change or no one layout to change.
|
|
554
703
|
*/ async function aiNavigationLayoutWrite(firestore, input) {
|
|
555
704
|
var _input_storeLinks;
|
|
556
|
-
if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.storeLinks) return null;
|
|
705
|
+
if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.droppedScreenIds.length && !input.storeLinks) return null;
|
|
557
706
|
const layoutId = await aiPagesLayoutId(firestore, input.hostId, input.pageIds);
|
|
558
707
|
if (!layoutId) return null;
|
|
559
708
|
return aiLayoutVersionWrite(firestore, {
|