@aglyn/plugins-ai 1.0.0-beta.260 → 1.0.0-beta.262

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 (81) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.d.ts +21 -58
  3. package/src/lib/components/ai-brief-dialog.component.js +8 -132
  4. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  5. package/src/lib/components/ai-job-follow.component.js +2 -35
  6. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  7. package/src/lib/components/ai-job-plan.component.d.ts +2 -8
  8. package/src/lib/components/ai-job-plan.component.js +13 -74
  9. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  10. package/src/lib/components/ai-site-start-card.component.js +43 -34
  11. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  12. package/src/lib/components/ai-template-collection-field.component.d.ts +2 -12
  13. package/src/lib/components/ai-template-collection-field.component.js +13 -10
  14. package/src/lib/components/ai-template-collection-field.component.js.map +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.d.ts +3 -25
  16. package/src/lib/components/ai-upsell-dialog.component.js +9 -66
  17. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  18. package/src/lib/components/use-ai-job-run.d.ts +3 -8
  19. package/src/lib/components/use-ai-job-run.js +4 -15
  20. package/src/lib/components/use-ai-job-run.js.map +1 -1
  21. package/src/lib/insights/ai-figure-readers.js +15 -5
  22. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  23. package/src/lib/insights/ai-figure-summaries.d.ts +27 -0
  24. package/src/lib/insights/ai-figure-summaries.js +79 -0
  25. package/src/lib/insights/ai-figure-summaries.js.map +1 -0
  26. package/src/lib/insights/ai-insight-readers.d.ts +2 -1
  27. package/src/lib/insights/ai-insight-readers.js +11 -26
  28. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  29. package/src/lib/jobs/ai-job-admission.d.ts +6 -0
  30. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  31. package/src/lib/jobs/ai-job-site-step.js +10 -6
  32. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  33. package/src/lib/jobs/ai-layout-stock-photos.d.ts +43 -11
  34. package/src/lib/jobs/ai-layout-stock-photos.js +329 -32
  35. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  36. package/src/lib/jobs/ai-stock-photo-picker.js +13 -7
  37. package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -1
  38. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.d.ts +35 -0
  39. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.js +404 -0
  40. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.js.map +1 -0
  41. package/src/lib/layout-language/ai-layout-motion.d.ts +9 -3
  42. package/src/lib/layout-language/ai-layout-motion.js +9 -3
  43. package/src/lib/layout-language/ai-layout-motion.js.map +1 -1
  44. package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
  45. package/src/lib/layout-language/ai-layout-pictures.js +12 -6
  46. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  47. package/src/lib/model/ai-brief-copy.d.ts +85 -0
  48. package/src/lib/model/ai-brief-copy.js +152 -0
  49. package/src/lib/model/ai-brief-copy.js.map +1 -0
  50. package/src/lib/model/ai-create-view.d.ts +200 -0
  51. package/src/lib/model/ai-create-view.js +263 -0
  52. package/src/lib/model/ai-create-view.js.map +1 -0
  53. package/src/lib/model/ai-insight.d.ts +1 -1
  54. package/src/lib/model/ai-insight.js +4 -39
  55. package/src/lib/model/ai-insight.js.map +1 -1
  56. package/src/lib/model/ai-job-activity.d.ts +24 -0
  57. package/src/lib/model/ai-job-activity.js +52 -1
  58. package/src/lib/model/ai-job-activity.js.map +1 -1
  59. package/src/lib/model/ai-job-plan-lines.d.ts +46 -0
  60. package/src/lib/model/ai-job-plan-lines.js +70 -0
  61. package/src/lib/model/ai-job-plan-lines.js.map +1 -0
  62. package/src/lib/model/ai-template-collections.d.ts +28 -0
  63. package/src/lib/model/ai-template-collections.js +28 -0
  64. package/src/lib/model/ai-template-collections.js.map +1 -0
  65. package/src/lib/model/ai-upsell-copy.d.ts +41 -0
  66. package/src/lib/model/ai-upsell-copy.js +85 -0
  67. package/src/lib/model/ai-upsell-copy.js.map +1 -0
  68. package/src/lib/runtime/ai-doctrine-validators.d.ts +40 -0
  69. package/src/lib/runtime/ai-doctrine-validators.js +148 -3
  70. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  71. package/src/lib/runtime/ai-doctrine.js +6 -2
  72. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  73. package/src/lib/runtime/ai-eval-memory-firestore.js +2 -0
  74. package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -1
  75. package/src/lib/server/ai-create-route.d.ts +32 -0
  76. package/src/lib/server/ai-create-route.js +295 -0
  77. package/src/lib/server/ai-create-route.js.map +1 -0
  78. package/src/lib/server/ai-jobs-route.js +19 -0
  79. package/src/lib/server/ai-jobs-route.js.map +1 -1
  80. package/src/lib/server.js +14 -0
  81. package/src/lib/server.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n /** The wire summary's: the machine confirms this job's plan itself. */\n autoConfirm?: boolean\n /** When the job last moved; the wire summary's ISO string. */\n updatedAt?: unknown\n}\n\n/**\n * How long a self-confirming plan may sit parked before it reads as waiting\n * for a person after all: a confirmation that never came.\n */\nexport const AI_JOB_SELF_CONFIRM_GRACE_MS = 5 * 60_000\n\n/**\n * Whether a job is parked on a plan the machine is about to confirm itself\n * (AGL-3596). A guided site start records its plan step — parking the job on\n * the plan as every planned job does — and confirms it in the next write, so\n * a surface that reads in between sees `needs_review` for an instant. That\n * instant is the build going on, never a stop: read as one, it showed the\n * person a failed site that turned green again a moment later. The job's\n * `updatedAt` there is the clock its step was claimed at, so the grace covers\n * the plan step's own run as well as the confirmation. Past\n * {@link AI_JOB_SELF_CONFIRM_GRACE_MS} without the confirmation, the park is\n * what it says.\n */\nexport function aiJobConfirmingOwnPlan(\n job: Pick<AiJobPhaseSource, 'status' | 'review' | 'autoConfirm' | 'updatedAt'>,\n now: number = Date.now(),\n): boolean {\n if (job.status !== 'needs_review' || job.review?.reason !== 'plan' || job.autoConfirm !== true) return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return !Number.isFinite(at) || now - at < AI_JOB_SELF_CONFIRM_GRACE_MS\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review' && !aiJobConfirmingOwnPlan(job)) {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n logic: 'function',\n edit: 'change',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n build: 'build',\n navigation: 'menu',\n duplicate: 'copy',\n settings: 'settings',\n media: 'media library change',\n tracking: 'tracking',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n\n/**\n * The job kinds that build a site's pages (AGL-3765): a page, a guided start\n * and a build from the Assist chat. Two at once on one site each plan against\n * the site as it was, and race for what its plan holds one of.\n */\nexport const AI_PAGE_BUILDING_JOB_KINDS: readonly AiJobKind[] = ['page', 'site', 'build']\n\n/**\n * How long a plan waiting for its confirmation still counts as a build on its\n * way (AGL-3765): one the person is reading now, not one left unconfirmed\n * days ago.\n */\nexport const AI_SITE_BUILD_PLAN_FRESH_MS = 15 * 60_000\n\n/** What a door that starts a page says while another is being built on the site (AGL-3765). */\nexport const AI_SITE_BUILD_IN_FLIGHT_COPY = 'A page is already being built. Wait for it, or start another anyway.'\n\n/**\n * Another job building pages on this site (AGL-3765): queued, planning or\n * building, or a plan that waits for its confirmation and moved in the last\n * {@link AI_SITE_BUILD_PLAN_FRESH_MS}. `null` when there is none. A door warns\n * with it and still starts the job: two builds can share a site, they just\n * cannot both make what its plan holds only one of.\n */\nexport function aiSiteBuildInFlight<\n T extends AiJobPhaseSource & { id: string; kind: AiJobKind | string; hostId?: string | null },\n>(\n jobs: readonly T[],\n input: { hostId: string | null | undefined; exceptJobId?: string | null; now?: number },\n): T | null {\n if (!input.hostId) return null\n const now = input.now ?? Date.now()\n return (\n jobs.find((job) => {\n if (job.id === input.exceptJobId || job.hostId !== input.hostId) return false\n if (!AI_PAGE_BUILDING_JOB_KINDS.includes(job.kind as AiJobKind)) return false\n const phase = aiJobPhase(job)\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return true\n if (phase !== 'plan-ready') return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return Number.isFinite(at) && now - at < AI_SITE_BUILD_PLAN_FRESH_MS\n }) ?? null\n )\n}\n"],"names":["AI_JOB_PLAN_STEP","AI_JOB_SELF_CONFIRM_GRACE_MS","aiJobConfirmingOwnPlan","job","now","Date","status","review","reason","autoConfirm","at","updatedAt","parse","NaN","Number","isFinite","aiJobPhase","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","logic","edit","theme","products","experiment","insight","build","navigation","duplicate","settings","media","tracking","aiJobKindNoun","kind","AI_PAGE_BUILDING_JOB_KINDS","AI_SITE_BUILD_PLAN_FRESH_MS","AI_SITE_BUILD_IN_FLIGHT_COPY","aiSiteBuildInFlight","input","hostId","id","exceptJobId","includes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n AI_JOB_TERMINAL_STATUSES,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n /** The wire summary's: the machine confirms this job's plan itself. */\n autoConfirm?: boolean\n /** When the job last moved; the wire summary's ISO string. */\n updatedAt?: unknown\n}\n\n/**\n * How long a self-confirming plan may sit parked before it reads as waiting\n * for a person after all: a confirmation that never came.\n */\nexport const AI_JOB_SELF_CONFIRM_GRACE_MS = 5 * 60_000\n\n/**\n * Whether a job is parked on a plan the machine is about to confirm itself\n * (AGL-3596). A guided site start records its plan step — parking the job on\n * the plan as every planned job does — and confirms it in the next write, so\n * a surface that reads in between sees `needs_review` for an instant. That\n * instant is the build going on, never a stop: read as one, it showed the\n * person a failed site that turned green again a moment later. The job's\n * `updatedAt` there is the clock its step was claimed at, so the grace covers\n * the plan step's own run as well as the confirmation. Past\n * {@link AI_JOB_SELF_CONFIRM_GRACE_MS} without the confirmation, the park is\n * what it says.\n */\nexport function aiJobConfirmingOwnPlan(\n job: Pick<AiJobPhaseSource, 'status' | 'review' | 'autoConfirm' | 'updatedAt'>,\n now: number = Date.now(),\n): boolean {\n if (job.status !== 'needs_review' || job.review?.reason !== 'plan' || job.autoConfirm !== true) return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return !Number.isFinite(at) || now - at < AI_JOB_SELF_CONFIRM_GRACE_MS\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review' && !aiJobConfirmingOwnPlan(job)) {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n logic: 'function',\n edit: 'change',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n build: 'build',\n navigation: 'menu',\n duplicate: 'copy',\n settings: 'settings',\n media: 'media library change',\n tracking: 'tracking',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n\n/**\n * The job kinds that build a site's pages (AGL-3765): a page, a guided start\n * and a build from the Assist chat. Two at once on one site each plan against\n * the site as it was, and race for what its plan holds one of.\n */\nexport const AI_PAGE_BUILDING_JOB_KINDS: readonly AiJobKind[] = ['page', 'site', 'build']\n\n/**\n * How long a plan waiting for its confirmation still counts as a build on its\n * way (AGL-3765): one the person is reading now, not one left unconfirmed\n * days ago.\n */\nexport const AI_SITE_BUILD_PLAN_FRESH_MS = 15 * 60_000\n\n/** What a door that starts a page says while another is being built on the site (AGL-3765). */\nexport const AI_SITE_BUILD_IN_FLIGHT_COPY = 'A page is already being built. Wait for it, or start another anyway.'\n\n/**\n * Another job building pages on this site (AGL-3765): queued, planning or\n * building, or a plan that waits for its confirmation and moved in the last\n * {@link AI_SITE_BUILD_PLAN_FRESH_MS}. `null` when there is none. A door warns\n * with it and still starts the job: two builds can share a site, they just\n * cannot both make what its plan holds only one of.\n */\nexport function aiSiteBuildInFlight<\n T extends AiJobPhaseSource & { id: string; kind: AiJobKind | string; hostId?: string | null },\n>(\n jobs: readonly T[],\n input: { hostId: string | null | undefined; exceptJobId?: string | null; now?: number },\n): T | null {\n if (!input.hostId) return null\n const now = input.now ?? Date.now()\n return (\n jobs.find((job) => {\n if (job.id === input.exceptJobId || job.hostId !== input.hostId) return false\n if (!AI_PAGE_BUILDING_JOB_KINDS.includes(job.kind as AiJobKind)) return false\n const phase = aiJobPhase(job)\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return true\n if (phase !== 'plan-ready') return false\n const at = typeof job.updatedAt === 'string' ? Date.parse(job.updatedAt) : NaN\n return Number.isFinite(at) && now - at < AI_SITE_BUILD_PLAN_FRESH_MS\n }) ?? null\n )\n}\n\n/** The fields of a job {@link aiJobSettled} and {@link aiJobProblem} read. */\nexport interface AiJobSettleSource {\n status: AiJobStatus\n error?: string | null\n review?: { message?: string | null } | null\n}\n\n/** Whether a job has stopped moving: finished, or waiting on something only a person can change. */\nexport function aiJobSettled(job: AiJobSettleSource | null): boolean {\n if (!job) return false\n return (\n AI_JOB_TERMINAL_STATUSES.includes(job.status) ||\n job.status === 'needs_input' ||\n job.status === 'needs_review'\n )\n}\n\n/**\n * Why a settled job has no result to show, in words a person can act on, or\n * `null` for a job that finished.\n */\nexport function aiJobProblem(job: AiJobSettleSource | null, failed: string): string | null {\n if (!job || !aiJobSettled(job) || job.status === 'done') return null\n if (job.status === 'canceled') return 'The job was canceled.'\n if (job.status === 'needs_review') return job.review?.message || job.error || failed\n return job.error || failed\n}\n\nexport type AiFollowTone = 'info' | 'warning' | 'success' | 'error'\n\n/**\n * What the follow card's status line says in each phase, and how loudly\n * (AGL-3593): the dialog that started a job, and the apps' follow view.\n */\nexport const AI_JOB_FOLLOW_PHASE_LINES: Readonly<Record<AiJobPhase, { severity: AiFollowTone; text: string }>> = {\n queued: { severity: 'info', text: 'Queued. It starts in a moment.' },\n planning: { severity: 'info', text: 'Planning. The plan appears here when it is ready.' },\n 'plan-ready': {\n severity: 'warning',\n text: 'Your plan is ready. Read it below and confirm it to build — nothing is built until you do.',\n },\n building: { severity: 'info', text: 'Building. Everything it builds is an unpublished draft.' },\n attention: { severity: 'warning', text: 'The job stopped and needs you.' },\n done: { severity: 'success', text: 'Done. Your drafts are ready to open.' },\n failed: { severity: 'error', text: 'The job stopped before it finished.' },\n canceled: { severity: 'info', text: 'The job was canceled.' },\n}\n"],"names":["AI_JOB_PLAN_STEP","AI_JOB_TERMINAL_STATUSES","AI_JOB_SELF_CONFIRM_GRACE_MS","aiJobConfirmingOwnPlan","job","now","Date","status","review","reason","autoConfirm","at","updatedAt","parse","NaN","Number","isFinite","aiJobPhase","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","logic","edit","theme","products","experiment","insight","build","navigation","duplicate","settings","media","tracking","aiJobKindNoun","kind","AI_PAGE_BUILDING_JOB_KINDS","AI_SITE_BUILD_PLAN_FRESH_MS","AI_SITE_BUILD_IN_FLIGHT_COPY","aiSiteBuildInFlight","input","hostId","id","exceptJobId","includes","aiJobSettled","aiJobProblem","message","error","AI_JOB_FOLLOW_PHASE_LINES","severity","text"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type AiBuildPlanEmbed } from './ai-build-plan';
18
+ import type { AiJobPlanSummary } from './ai-jobs.types';
19
+ /**
20
+ * A JOB'S PLAN AS THE LINES A PERSON READS BEFORE CONFIRMING IT (AGL-2935):
21
+ * what it reuses from the site, what it creates and why, the pages it builds,
22
+ * the items, the players it embeds and the store pages the platform adds. The
23
+ * console's plan card draws these, and the native apps' follow view sends them
24
+ * (AGL-3854), so both say the same sentences.
25
+ */
26
+ /** One line of a plan. */
27
+ export interface AiJobPlanLine {
28
+ /** Stable within one plan, for a list's key. */
29
+ key: string;
30
+ text: string;
31
+ /** Added by the platform rather than the model (a store's own pages, AGL-3676): it uses no AI credits. */
32
+ platformAdded?: boolean;
33
+ }
34
+ /** A reference as a person reads it: the record's name, or what the plan creates. */
35
+ export declare function aiPlanRefName(plan: Pick<AiJobPlanSummary, 'labels'> | null, ref: string | null): string;
36
+ /**
37
+ * A third-party player the plan lists, with what it costs the page, read
38
+ * before the member confirms it (rule 16, AGL-3433). The cost is the
39
+ * platform's sentence, never the model's: the player is framed lazily, so its
40
+ * host's code loads when a visitor reaches it, whether or not they press play.
41
+ */
42
+ export declare function aiPlanEmbedLine(embed: AiBuildPlanEmbed, named: (ref: string) => string): string;
43
+ /** The plan's heading: whether it is still a proposal. */
44
+ export declare function aiJobPlanHeading(plan: Pick<AiJobPlanSummary, 'status'>): string;
45
+ /** Every line of a plan, in the order the plan card lists them. */
46
+ export declare function aiJobPlanLines(plan: AiJobPlanSummary): AiJobPlanLine[];
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, isAiPlanNewRef } from "./ai-build-plan.js";
17
+ import { aiBuildOpNoun } from "./ai-build-progress.js";
18
+ import { aiBuildStorePagesAdded } from "./ai-build-store-pages.js";
19
+ /** A reference as a person reads it: the record's name, or what the plan creates. */ export function aiPlanRefName(plan, ref) {
20
+ var _ref;
21
+ if (!ref) return '';
22
+ return isAiPlanNewRef(ref) ? ref.slice('new:'.length) : (_ref = plan == null ? void 0 : plan.labels[ref]) != null ? _ref : ref;
23
+ }
24
+ /**
25
+ * A third-party player the plan lists, with what it costs the page, read
26
+ * before the member confirms it (rule 16, AGL-3433). The cost is the
27
+ * platform's sentence, never the model's: the player is framed lazily, so its
28
+ * host's code loads when a visitor reaches it, whether or not they press play.
29
+ */ export function aiPlanEmbedLine(embed, named) {
30
+ const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host];
31
+ const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where;
32
+ const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste';
33
+ return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`;
34
+ }
35
+ /** The plan's heading: whether it is still a proposal. */ export function aiJobPlanHeading(plan) {
36
+ return plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan';
37
+ }
38
+ /** Every line of a plan, in the order the plan card lists them. */ export function aiJobPlanLines(plan) {
39
+ var _plan_items, _plan_embeds;
40
+ const named = (ref)=>aiPlanRefName(plan, ref);
41
+ return [
42
+ ...plan.reuse.map((entry, index)=>({
43
+ key: `reuse-${index}`,
44
+ text: `Reuses the ${entry.kind === 'screen' ? 'page' : entry.kind} ${named(entry.id)} — ${entry.purpose}`
45
+ })),
46
+ ...plan.create.map((entry, index)=>({
47
+ key: `create-${index}`,
48
+ text: `Creates the ${AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} ${entry.name}` + `${entry.duplicateOf ? `, from a copy of ${named(entry.duplicateOf)}` : ''} — ${entry.why}`
49
+ })),
50
+ ...plan.screens.map((screen, index)=>({
51
+ key: `screen-${index}`,
52
+ text: (screen.record ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…` : `Builds the page ${screen.title} at ${screen.slug}`) + (screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '') + (screen.layout ? ` in ${named(screen.layout)}` : '') + (screen.sections.length ? `: ${screen.sections.map((section)=>section.name).join(', ')}` : '')
53
+ })),
54
+ ...((_plan_items = plan.items) != null ? _plan_items : []).map((item)=>({
55
+ key: `item-${item.slot}`,
56
+ text: `Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}${item.why ? ` — ${item.why}` : ''}`
57
+ })),
58
+ ...((_plan_embeds = plan.embeds) != null ? _plan_embeds : []).map((embed, index)=>({
59
+ key: `embed-${index}`,
60
+ text: aiPlanEmbedLine(embed, named)
61
+ })),
62
+ ...aiBuildStorePagesAdded(plan).map((page)=>({
63
+ key: `store-${page.key}`,
64
+ text: `Adds the page ${page.title} at ${page.href} — added for your store automatically, uses no AI credits`,
65
+ platformAdded: true
66
+ }))
67
+ ];
68
+ }
69
+
70
+ //# sourceMappingURL=ai-job-plan-lines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-plan-lines.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\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from './ai-build-plan'\nimport { aiBuildOpNoun } from './ai-build-progress'\nimport { aiBuildStorePagesAdded } from './ai-build-store-pages'\nimport type { AiJobPlanSummary } from './ai-jobs.types'\n\n/**\n * A JOB'S PLAN AS THE LINES A PERSON READS BEFORE CONFIRMING IT (AGL-2935):\n * what it reuses from the site, what it creates and why, the pages it builds,\n * the items, the players it embeds and the store pages the platform adds. The\n * console's plan card draws these, and the native apps' follow view sends them\n * (AGL-3854), so both say the same sentences.\n */\n\n/** One line of a plan. */\nexport interface AiJobPlanLine {\n /** Stable within one plan, for a list's key. */\n key: string\n text: string\n /** Added by the platform rather than the model (a store's own pages, AGL-3676): it uses no AI credits. */\n platformAdded?: boolean\n}\n\n/** A reference as a person reads it: the record's name, or what the plan creates. */\nexport function aiPlanRefName(plan: Pick<AiJobPlanSummary, 'labels'> | null, ref: string | null): string {\n if (!ref) return ''\n return isAiPlanNewRef(ref) ? ref.slice('new:'.length) : (plan?.labels[ref] ?? ref)\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`\n}\n\n/** The plan's heading: whether it is still a proposal. */\nexport function aiJobPlanHeading(plan: Pick<AiJobPlanSummary, 'status'>): string {\n return plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'\n}\n\n/** Every line of a plan, in the order the plan card lists them. */\nexport function aiJobPlanLines(plan: AiJobPlanSummary): AiJobPlanLine[] {\n const named = (ref: string | null) => aiPlanRefName(plan, ref)\n return [\n ...plan.reuse.map((entry, index) => ({\n key: `reuse-${index}`,\n text: `Reuses the ${entry.kind === 'screen' ? 'page' : entry.kind} ${named(entry.id)} — ${entry.purpose}`,\n })),\n ...plan.create.map((entry, index) => ({\n key: `create-${index}`,\n text:\n `Creates the ${AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} ${entry.name}` +\n `${entry.duplicateOf ? `, from a copy of ${named(entry.duplicateOf)}` : ''} — ${entry.why}`,\n })),\n ...plan.screens.map((screen, index) => ({\n key: `screen-${index}`,\n text:\n (screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`) +\n (screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '') +\n (screen.layout ? ` in ${named(screen.layout)}` : '') +\n (screen.sections.length ? `: ${screen.sections.map((section) => section.name).join(', ')}` : ''),\n })),\n ...(plan.items ?? []).map((item) => ({\n key: `item-${item.slot}`,\n text: `Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}${item.why ? ` — ${item.why}` : ''}`,\n })),\n ...(plan.embeds ?? []).map((embed, index) => ({\n key: `embed-${index}`,\n text: aiPlanEmbedLine(embed, named),\n })),\n ...aiBuildStorePagesAdded(plan).map((page) => ({\n key: `store-${page.key}`,\n text: `Adds the page ${page.title} at ${page.href} — added for your store automatically, uses no AI credits`,\n platformAdded: true,\n })),\n ]\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiBuildOpNoun","aiBuildStorePagesAdded","aiPlanRefName","plan","ref","slice","length","labels","aiPlanEmbedLine","embed","named","host","where","link","url","asked","aiJobPlanHeading","status","aiJobPlanLines","reuse","map","entry","index","key","text","kind","id","purpose","create","noun","name","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","join","items","item","slot","op","toLowerCase","embeds","page","href","platformAdded"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** A content collection as the field offers it. */
18
+ export interface AiTemplateCollectionOption {
19
+ id: string;
20
+ name: string;
21
+ }
22
+ /**
23
+ * The content collections among a site's collection rows, by name, each
24
+ * named as the template step names it: display name, name, slug, then id.
25
+ */
26
+ export declare function aiTemplateCollectionOptions(rows: ReadonlyArray<Record<string, unknown> & {
27
+ $id: string;
28
+ }>): AiTemplateCollectionOption[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { hostCollectionKind } from "@aglyn/aglyn/app-utils/collection-kind";
17
+ /**
18
+ * The content collections among a site's collection rows, by name, each
19
+ * named as the template step names it: display name, name, slug, then id.
20
+ */ export function aiTemplateCollectionOptions(rows) {
21
+ const text = (value)=>typeof value === 'string' ? value.trim() : '';
22
+ return rows.filter((row)=>hostCollectionKind(row) === 'content').map((row)=>({
23
+ id: row.$id,
24
+ name: text(row['displayName']) || text(row['name']) || text(row['slug']) || row.$id
25
+ })).sort((a, b)=>a.name.localeCompare(b.name));
26
+ }
27
+
28
+ //# sourceMappingURL=ai-template-collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-template-collections.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 { hostCollectionKind } from '@aglyn/aglyn/app-utils/collection-kind'\n\n/** A content collection as the field offers it. */\nexport interface AiTemplateCollectionOption {\n id: string\n name: string\n}\n\n/**\n * The content collections among a site's collection rows, by name, each\n * named as the template step names it: display name, name, slug, then id.\n */\nexport function aiTemplateCollectionOptions(\n rows: ReadonlyArray<Record<string, unknown> & { $id: string }>,\n): AiTemplateCollectionOption[] {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n return rows\n .filter((row) => hostCollectionKind(row) === 'content')\n .map((row) => ({\n id: row.$id,\n name: text(row['displayName']) || text(row['name']) || text(row['slug']) || row.$id,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n"],"names":["hostCollectionKind","aiTemplateCollectionOptions","rows","text","value","trim","filter","row","map","id","$id","name","sort","a","b","localeCompare"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kBAAkB,QAAQ,yCAAwC;AAQ3E;;;CAGC,GACD,OAAO,SAASC,4BACdC,IAA8D;IAE9D,MAAMC,OAAO,CAACC,QAAoB,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;IAC7E,OAAOH,KACJI,MAAM,CAAC,CAACC,MAAQP,mBAAmBO,SAAS,WAC5CC,GAAG,CAAC,CAACD,MAAS,CAAA;YACbE,IAAIF,IAAIG,GAAG;YACXC,MAAMR,KAAKI,GAAG,CAAC,cAAc,KAAKJ,KAAKI,GAAG,CAAC,OAAO,KAAKJ,KAAKI,GAAG,CAAC,OAAO,KAAKA,IAAIG,GAAG;QACrF,CAAA,GACCE,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEF,IAAI,CAACI,aAAa,CAACD,EAAEH,IAAI;AAC/C"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiBriefKind } from './ai-brief-copy';
18
+ /**
19
+ * What a "Create with AI" or "Ask AI" entry makes: the brief kinds, an
20
+ * automation, products, a function or variable, an overlay, and an answer
21
+ * about the figures on a page.
22
+ */
23
+ export type AiUpsellKind = AiBriefKind | 'workflow' | 'product' | 'logic' | 'overlay' | 'funnel' | 'insight';
24
+ /** What one entry's dialog says. */
25
+ export interface AiUpsellCopy {
26
+ title: string;
27
+ does: string;
28
+ /** The entry's own label, which the button reads and the offer names; "Create with AI" when absent. */
29
+ label?: string;
30
+ }
31
+ /** The label every "Create with AI" entry reads. */
32
+ export declare const AI_UPSELL_CREATE_LABEL = "Create with AI";
33
+ /** What the dialog says one entry would do here. */
34
+ export declare const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>>;
35
+ /** The label an entry's button reads. */
36
+ export declare function aiUpsellLabel(kind: AiUpsellKind): string;
37
+ /**
38
+ * The sentence that says where the feature comes from, and who can add it.
39
+ * `feature` is what the entry is called: "Create with AI" unless it is an ask.
40
+ */
41
+ export declare function aiUpsellOffer(canManageBilling: boolean, feature?: string): string;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { aiAddonName } from "@aglyn/aglyn/app-utils/plan-entitlements";
17
+ /** The label every "Create with AI" entry reads. */ export const AI_UPSELL_CREATE_LABEL = 'Create with AI';
18
+ /** What the dialog says one entry would do here. */ export const AI_UPSELL_COPY = {
19
+ page: {
20
+ title: 'Create a page with AI',
21
+ does: 'Describe the page you need and AI plans it from your brief, using your theme and ' + 'what the site already has. You confirm the plan, and the page is built as an ' + 'unpublished draft.'
22
+ },
23
+ template: {
24
+ title: 'Create a page template with AI',
25
+ does: 'Describe what each page should show and AI plans the template, filling what ' + 'changes from page to page from each record. You confirm the plan, and the template ' + 'is built as a draft in your library.'
26
+ },
27
+ layout: {
28
+ title: 'Create a layout with AI',
29
+ does: 'Describe what the layout should hold and AI plans it, linking your pages and placing ' + 'your navigation or menu component when the site has one. You confirm the plan, and ' + 'the layout is built as a draft no page uses until you assign it.'
30
+ },
31
+ form: {
32
+ title: 'Create a form with AI',
33
+ does: 'Describe what the form is for and AI plans its fields, its marketing consent and ' + 'where each submission goes. You confirm the plan, and the form is built as a draft.'
34
+ },
35
+ component: {
36
+ title: 'Create a reusable component with AI',
37
+ does: 'Describe what the component should show and AI plans it, making what each page can ' + 'change into its properties. You confirm the plan, and the component is built as a ' + 'draft.'
38
+ },
39
+ workflow: {
40
+ title: 'Create an automation with AI',
41
+ does: 'Describe what should happen, and when, and AI drafts the automation from the ' + 'triggers and steps your plan includes. It is saved switched off for you to review.'
42
+ },
43
+ email: {
44
+ title: 'Create an email with AI',
45
+ does: 'Describe the email you need and AI plans a design from the email blocks, with three ' + 'subject lines and three preheaders to choose between. You confirm the plan, and the ' + 'design is built as a draft that is sent to nobody.'
46
+ },
47
+ campaign: {
48
+ title: 'Create a campaign with AI',
49
+ does: 'Describe what the campaign is for and AI writes its email design and a draft campaign ' + 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' + 'until you pick its lists.'
50
+ },
51
+ product: {
52
+ title: 'Create products with AI',
53
+ does: 'Describe what the store sells and AI proposes up to twelve products. You review them ' + 'before any is created, and each is created as a draft at a suggested price that is not on ' + 'your storefront until you review it and activate it.'
54
+ },
55
+ logic: {
56
+ title: 'Create a function or variable with AI',
57
+ does: 'Describe what it should work out or hold and AI drafts the function or variable. It ' + 'opens unsaved in the editor for you to check and save.'
58
+ },
59
+ overlay: {
60
+ title: 'Create an overlay with AI',
61
+ does: 'Describe the announcement bar or popup you want and AI writes its copy within the ' + 'overlay’s limits. It is saved switched off, for you to check and turn on.'
62
+ },
63
+ funnel: {
64
+ title: 'Create a funnel with AI',
65
+ does: 'Describe the journey you want to measure and AI proposes its steps from the site’s ' + 'real pages, forms and products. It opens in the funnel editor for you to check and save.'
66
+ },
67
+ insight: {
68
+ title: 'Ask AI about these numbers',
69
+ label: 'Ask a question',
70
+ does: 'Ask a question about the figures on this page and AI answers from them, every answer ' + 'traced to the figures it is built from. It reads; it changes nothing.'
71
+ }
72
+ };
73
+ /** The label an entry's button reads. */ export function aiUpsellLabel(kind) {
74
+ var _AI_UPSELL_COPY_kind_label;
75
+ return (_AI_UPSELL_COPY_kind_label = AI_UPSELL_COPY[kind].label) != null ? _AI_UPSELL_COPY_kind_label : AI_UPSELL_CREATE_LABEL;
76
+ }
77
+ /**
78
+ * The sentence that says where the feature comes from, and who can add it.
79
+ * `feature` is what the entry is called: "Create with AI" unless it is an ask.
80
+ */ export function aiUpsellOffer(canManageBilling, feature = AI_UPSELL_CREATE_LABEL) {
81
+ const addon = aiAddonName();
82
+ return canManageBilling ? `${feature} comes with the ${addon} add-on, which you can add to this ` + 'workspace from Billing.' : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` + 'to add it from Billing.';
83
+ }
84
+
85
+ //# sourceMappingURL=ai-upsell-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-upsell-copy.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 { aiAddonName } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AiBriefKind } from './ai-brief-copy'\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft at a suggested price that is not on ' +\n 'your storefront until you review it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n"],"names":["aiAddonName","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,2CAA0C;AAyBtE,kDAAkD,GAClD,OAAO,MAAMC,yBAAyB,iBAAgB;AAEtD,kDAAkD,GAClD,OAAO,MAAMC,iBAA+D;IAC1EC,MAAM;QACJC,OAAO;QACPC,MACE,sFACA,kFACA;IACJ;IACAC,UAAU;QACRF,OAAO;QACPC,MACE,iFACA,wFACA;IACJ;IACAE,QAAQ;QACNH,OAAO;QACPC,MACE,0FACA,wFACA;IACJ;IACAG,MAAM;QACJJ,OAAO;QACPC,MACE,sFACA;IACJ;IACAI,WAAW;QACTL,OAAO;QACPC,MACE,wFACA,uFACA;IACJ;IACAK,UAAU;QACRN,OAAO;QACPC,MACE,kFACA;IACJ;IACAM,OAAO;QACLP,OAAO;QACPC,MACE,yFACA,yFACA;IACJ;IACAO,UAAU;QACRR,OAAO;QACPC,MACE,2FACA,2FACA;IACJ;IACAQ,SAAS;QACPT,OAAO;QACPC,MACE,0FACA,+FACA;IACJ;IACAS,OAAO;QACLV,OAAO;QACPC,MACE,yFACA;IACJ;IACAU,SAAS;QACPX,OAAO;QACPC,MACE,uFACA;IACJ;IACAW,QAAQ;QACNZ,OAAO;QACPC,MACE,wFACA;IACJ;IACAY,SAAS;QACPb,OAAO;QACPc,OAAO;QACPb,MACE,0FACA;IACJ;AACF,EAAC;AAED,uCAAuC,GACvC,OAAO,SAASc,cAAcC,IAAkB;QACvClB;IAAP,QAAOA,6BAAAA,cAAc,CAACkB,KAAK,CAACF,KAAK,YAA1BhB,6BAA8BD;AACvC;AAEA;;;CAGC,GACD,OAAO,SAASoB,cAAcC,gBAAyB,EAAEC,UAAUtB,sBAAsB;IACvF,MAAMuB,QAAQxB;IACd,OAAOsB,mBACH,GAAGC,QAAQ,gBAAgB,EAAEC,MAAM,mCAAmC,CAAC,GACrE,4BACF,GAAGD,QAAQ,gBAAgB,EAAEC,MAAM,wCAAwC,CAAC,GAC1E;AACR"}
@@ -898,6 +898,25 @@ export declare function detectPlanSplitLists(plan: AiBuildPlan, inventory: AiSit
898
898
  * can answer with the form's bare name, which is no reference at all.
899
899
  */
900
900
  export declare function aiSettlePlanRefs(plan: AiBuildPlan, inventory?: AiSiteInventory | null): AiBuildPlan;
901
+ /**
902
+ * References to nothing at all are taken out before the rules read the plan
903
+ * (AGL-3845), where each has one reading. The live Free candle-shop start of
904
+ * 2026-10-16, on an empty site, reused a layout `laySite` and duplicated a
905
+ * page `scrStarter` the site does not have, and wrote what each section
906
+ * shows into its uses ("hero", "product grid", "help text"). It was refused
907
+ * for the unknown records, and the Free wall's trim, which never takes a
908
+ * section that places something, could take none of them.
909
+ *
910
+ * - A reuse of a record the site does not have is dropped, and a page laid
911
+ * out in it names no layout, for the layouts settle to place.
912
+ * - A page duplicated from a page the site does not have starts fresh.
913
+ * - A section's use that names no record the site has and no creation of the
914
+ * plan is a description, not a reference, and comes out.
915
+ *
916
+ * Runs after `aiSettlePlanRefs`, so a creation spelled another way is
917
+ * already pointed at, never dropped.
918
+ */
919
+ export declare function aiSettlePlanUnknownIds(plan: AiBuildPlan, inventory: AiSiteInventory | null): AiBuildPlan;
901
920
  /**
902
921
  * What customers say, which no dataset of the site's holds: a review, a
903
922
  * testimonial, a rating, a quote (AGL-3616). A job never writes them for the
@@ -1076,6 +1095,27 @@ export declare function detectCreateBeforeReuse(plan: AiBuildPlan, inventory: Ai
1076
1095
  * creation, each saying why and what to do instead.
1077
1096
  */
1078
1097
  export declare function detectPlanUncreatable(plan: AiBuildPlan, capabilities: AiPlanCapabilities | null): AiDoctrineViolation[];
1098
+ /**
1099
+ * A section that places a creation the plan never declares is settled
1100
+ * before the rules read the plan, where it has one answer (AGL-3845). The
1101
+ * live candle-shop start of 2026-10-16 placed `new:contactForm` in its
1102
+ * "Care, shipping and contact" section on both answers and stopped needing
1103
+ * input: the camelCase name read as a component, which a site start may not
1104
+ * make, and the refusal itself said to take it out.
1105
+ *
1106
+ * - A name that reads as a form, where this job may make one more form, is
1107
+ * declared as one: a newsletter's takes an email, any other the contact
1108
+ * form's fields, named in words ("Contact form") with the reference
1109
+ * pointed at it.
1110
+ * - Anything else this job could not make one more of is taken out of the
1111
+ * section's uses, and the section is drawn on the page — unless the
1112
+ * section itself is the form ("consultation request form"): without it
1113
+ * the section has no purpose, and what to put there instead is the
1114
+ * rule's to ask.
1115
+ * - A component the job could declare is left for the rule to ask about:
1116
+ * what it should be has more than one answer.
1117
+ */
1118
+ export declare function aiSettlePlanUndeclaredUses(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
1079
1119
  /**
1080
1120
  * Rule 7 (plan): what a plan places, it reuses or it creates (AGL-3040). The
1081
1121
  * doctrine has a plan refer to what it creates as new:<name>, so a reference