@aglyn/plugins-ai 1.0.0-beta.227 → 1.0.0-beta.229

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 (109) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -17
  3. package/src/lib/components/ai-brief-dialog.component.js +1 -1
  4. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  5. package/src/lib/components/ai-describe-automation.component.js +7 -2
  6. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  7. package/src/lib/components/ai-describe-button.component.js +6 -1
  8. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  9. package/src/lib/components/ai-describe-page.component.d.ts +1 -1
  10. package/src/lib/components/ai-describe-page.component.js +1 -1
  11. package/src/lib/components/ai-describe-page.component.js.map +1 -1
  12. package/src/lib/components/ai-job-events.d.ts +7 -1
  13. package/src/lib/components/ai-job-events.js +4 -2
  14. package/src/lib/components/ai-job-events.js.map +1 -1
  15. package/src/lib/components/ai-job-site.d.ts +22 -0
  16. package/src/lib/components/ai-job-site.js +73 -0
  17. package/src/lib/components/ai-job-site.js.map +1 -0
  18. package/src/lib/components/ai-jobs-list-page.component.d.ts +23 -0
  19. package/src/lib/components/ai-jobs-list-page.component.js +314 -0
  20. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -0
  21. package/src/lib/components/ai-jobs-page.component.d.ts +7 -0
  22. package/src/lib/components/ai-jobs-page.component.js +30 -0
  23. package/src/lib/components/ai-jobs-page.component.js.map +1 -0
  24. package/src/lib/components/ai-page-brief-dialog.component.d.ts +1 -1
  25. package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
  26. package/src/lib/components/ai-site-build-page.component.d.ts +31 -3
  27. package/src/lib/components/ai-site-build-page.component.js +394 -243
  28. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  29. package/src/lib/components/ai-site-start-card.component.d.ts +18 -1
  30. package/src/lib/components/ai-site-start-card.component.js +27 -15
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/assist-jobs-drawer.component.d.ts +11 -0
  33. package/src/lib/components/assist-jobs-drawer.component.js +37 -1
  34. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  35. package/src/lib/components/assist-panel.component.js +9 -4
  36. package/src/lib/components/assist-panel.component.js.map +1 -1
  37. package/src/lib/jobs/ai-job-draft-ids.d.ts +6 -0
  38. package/src/lib/jobs/ai-job-draft-ids.js +9 -0
  39. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  40. package/src/lib/jobs/ai-job-drafts.d.ts +7 -0
  41. package/src/lib/jobs/ai-job-drafts.js +16 -7
  42. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  43. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +70 -0
  44. package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
  45. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
  46. package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
  47. package/src/lib/jobs/ai-job-layout-step.js +21 -2
  48. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-sections.d.ts +6 -5
  50. package/src/lib/jobs/ai-job-page-sections.js +3 -3
  51. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  52. package/src/lib/jobs/ai-job-page-step.d.ts +19 -1
  53. package/src/lib/jobs/ai-job-page-step.js +50 -8
  54. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-plan-step.js +7 -2
  56. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-step.d.ts +21 -0
  58. package/src/lib/jobs/ai-job-site-step.js +103 -17
  59. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-text-step.d.ts +7 -1
  61. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  62. package/src/lib/jobs/ai-jobs.d.ts +19 -1
  63. package/src/lib/jobs/ai-jobs.js +130 -53
  64. package/src/lib/jobs/ai-jobs.js.map +1 -1
  65. package/src/lib/jobs/ai-site-publish.d.ts +98 -0
  66. package/src/lib/jobs/ai-site-publish.js +372 -0
  67. package/src/lib/jobs/ai-site-publish.js.map +1 -0
  68. package/src/lib/jobs/fixtures/ai-page-briefs.d.ts +7 -6
  69. package/src/lib/jobs/fixtures/ai-page-briefs.js +23 -56
  70. package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -1
  71. package/src/lib/model/ai-job-failure-copy.d.ts +20 -1
  72. package/src/lib/model/ai-job-failure-copy.js +28 -1
  73. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  74. package/src/lib/model/ai-job-notice.d.ts +6 -1
  75. package/src/lib/model/ai-job-notice.js +29 -3
  76. package/src/lib/model/ai-job-notice.js.map +1 -1
  77. package/src/lib/model/ai-jobs.types.d.ts +34 -0
  78. package/src/lib/model/ai-jobs.types.js.map +1 -1
  79. package/src/lib/model/ai-plan-capabilities.d.ts +25 -10
  80. package/src/lib/model/ai-plan-capabilities.js +15 -2
  81. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  82. package/src/lib/model/ai-site-build-progress.d.ts +25 -1
  83. package/src/lib/model/ai-site-build-progress.js +129 -15
  84. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  85. package/src/lib/model/ai-site-start-seo.d.ts +35 -0
  86. package/src/lib/model/ai-site-start-seo.js +145 -7
  87. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  88. package/src/lib/model/ai-site-start.d.ts +14 -0
  89. package/src/lib/model/ai-site-start.js +43 -0
  90. package/src/lib/model/ai-site-start.js.map +1 -1
  91. package/src/lib/plugin.js +18 -10
  92. package/src/lib/plugin.js.map +1 -1
  93. package/src/lib/runtime/ai-doctrine-validators.d.ts +16 -12
  94. package/src/lib/runtime/ai-doctrine-validators.js +19 -36
  95. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  96. package/src/lib/runtime/ai-doctrine.d.ts +6 -0
  97. package/src/lib/runtime/ai-doctrine.js +6 -2
  98. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  99. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  100. package/src/lib/runtime/ai-eval-live.js +1 -1
  101. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  102. package/src/lib/server/ai-jobs-live-sites.d.ts +26 -0
  103. package/src/lib/server/ai-jobs-live-sites.js +58 -0
  104. package/src/lib/server/ai-jobs-live-sites.js.map +1 -0
  105. package/src/lib/server/ai-jobs-route.js +22 -3
  106. package/src/lib/server/ai-jobs-route.js.map +1 -1
  107. package/src/lib/usage/assist-job-refund.d.ts +3 -2
  108. package/src/lib/usage/assist-job-refund.js +2 -1
  109. package/src/lib/usage/assist-job-refund.js.map +1 -1
@@ -16,6 +16,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
16
16
  * limitations under the License.
17
17
  */ import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
18
18
  import { duplicateResource } from "@aglyn/tenant-data-admin/server/duplicate-resource";
19
+ import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
19
20
  import { AI_STEP_TIERS } from "../providers/catalog.js";
20
21
  import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
21
22
  import { aiDoctrineTreeTool, runValidatedGeneration } from "../runtime/ai-doctrine.js";
@@ -23,10 +24,11 @@ import { walkTree } from "../runtime/ai-doctrine-validators.js";
23
24
  import { readSiteInventory } from "../runtime/site-inventory.js";
24
25
  import { registerAiJobAdmission } from "./ai-job-admission.js";
25
26
  import { aiBuildingPlanCreations, aiJobPlanCreationsRefusal, aiPlanCreationsRunMinimumMs } from "./ai-job-plan-creations.js";
26
- import { aiJobDraftId } from "./ai-job-draft-ids.js";
27
+ import { aiJobDraftId, aiOriginJobId } from "./ai-job-draft-ids.js";
27
28
  import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, readAiDraftNodes, writeAiDraft } from "./ai-job-drafts.js";
28
29
  import { aiBracketedFactsNote, aiCopySourceLines, aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
29
30
  import { aiPlanRegionViolations, aiPlannedLayoutRegions } from "./ai-job-plan-conformance.js";
31
+ import { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from "./ai-job-layout-site-pages.js";
30
32
  import { aiJobStepBudget } from "./ai-job-budget.js";
31
33
  import { registerAiJobStep } from "./ai-jobs.js";
32
34
  /**
@@ -87,6 +89,8 @@ import { registerAiJobStep } from "./ai-jobs.js";
87
89
  `Layout name: ${name}`,
88
90
  aiJobBriefLine(job),
89
91
  ...aiPlanReferenceLines(plan),
92
+ // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).
93
+ ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),
90
94
  ...source
91
95
  ].join('\n');
92
96
  }
@@ -306,6 +310,10 @@ export function createAiJobLayoutStep(deps = {}) {
306
310
  if (allowance) return aiUnspentOutcome(model, {
307
311
  review: aiLimitReview(allowance)
308
312
  });
313
+ // The pages a site scaffold builds after this layout (AGL-3596): their
314
+ // ids are the plan's, so the header links them by id, written by the
315
+ // platform before the doctrine checks the tree.
316
+ const sitePages = aiLayoutSitePages(job.inputs);
309
317
  const result = await runValidatedGeneration('layout', _extends({
310
318
  step: 'job.layout',
311
319
  model,
@@ -325,7 +333,17 @@ export function createAiJobLayoutStep(deps = {}) {
325
333
  effort: AI_ROUTING_TABLE['job.layout'].effort
326
334
  } : {}, {
327
335
  extend: aiLayoutReuseCheck(inventory, plan)
328
- }, signal ? {
336
+ }, sitePages.length ? {
337
+ context: {
338
+ screenIds: [
339
+ ...inventory.screens.map((screen)=>screen.id),
340
+ ...sitePages.map((page)=>page.id)
341
+ ]
342
+ },
343
+ complete: (tree)=>aiLayoutWithSitePages(tree, sitePages, {
344
+ homeScreenIds: aiHomeScreenIds(inventory)
345
+ })
346
+ } : {}, signal ? {
329
347
  signal
330
348
  } : {}));
331
349
  const spent = aiGenerationSpent(result);
@@ -343,6 +361,7 @@ export function createAiJobLayoutStep(deps = {}) {
343
361
  org,
344
362
  name,
345
363
  nodes: result.value.nodes,
364
+ aiJobId: aiOriginJobId(job),
346
365
  now
347
366
  });
348
367
  if (draft.ok === false) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [`Layout name: ${name}`, aiJobBriefLine(job), ...aiPlanReferenceLines(plan), ...source].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutReuseCheck(inventory, plan),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","result","step","instructions","messages","role","content","tool","thinking","effort","extend","spent","refused","value","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from './ai-job-layout-site-pages'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'> & Partial<Pick<AiJob, 'inputs'>>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [\n `Layout name: ${name}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).\n ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),\n ...source,\n ].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n // The pages a site scaffold builds after this layout (AGL-3596): their\n // ids are the plan's, so the header links them by id, written by the\n // platform before the doctrine checks the tree.\n const sitePages = aiLayoutSitePages(job.inputs)\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutReuseCheck(inventory, plan),\n ...(sitePages.length\n ? {\n context: { screenIds: [...inventory.screens.map((screen) => screen.id), ...sitePages.map((page) => page.id)] },\n complete: (tree: unknown) =>\n aiLayoutWithSitePages(tree, sitePages, { homeScreenIds: aiHomeScreenIds(inventory) }),\n }\n : {}),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","duplicateResource","aiHomeScreenIds","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiLayoutSitePages","aiLayoutSitePagesLines","aiLayoutWithSitePages","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","inputs","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","sitePages","result","step","instructions","messages","role","content","tool","thinking","effort","extend","screenIds","screens","screen","page","complete","homeScreenIds","spent","refused","value","aiJobId","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
@@ -96,15 +96,16 @@ export interface AiPageSectionPromptInput {
96
96
  /** The most elements the section may carry at this pass's answer ceiling. */
97
97
  maxElements: number;
98
98
  /**
99
- * Whether the workspace keeps reusable components and saved forms
100
- * (AGL-3030); `false` asks for the section built inline. Absent is `true`.
99
+ * Whether the workspace keeps reusable components (AGL-3030); `false`
100
+ * asks for a repeated item drawn in the section. Absent is `true`. A form
101
+ * is never drawn inline, whatever this says (AGL-3596).
101
102
  */
102
103
  reusableComponents?: boolean;
103
104
  /** The dataset this page is the record template of; absent for a page that is one page. */
104
105
  record?: AiPageRecordTemplate | null;
105
106
  }
106
- /** What a section request says where the workspace keeps no reusable components or saved forms. */
107
- export declare const AI_PAGE_SECTION_INLINE_LINE = "This site keeps no saved forms or reusable components: write a repeated item once, and draw a form as a Form holding its Form Fields.";
107
+ /** What a section request says where the workspace keeps no reusable components. */
108
+ export declare const AI_PAGE_SECTION_INLINE_LINE = "This site keeps no reusable components: write a repeated item once.";
108
109
  /**
109
110
  * How a repeated item is written once (AGL-3053): the item's subtree once,
110
111
  * with placeholders where its copies differ and their values listed, which
@@ -195,7 +196,7 @@ export declare function aiEmptyPage(): NodesMap;
195
196
  export declare function aiPageWithSection(page: NodesMap, section: AiPageSection, sectionIds: readonly string[]): NodesMap;
196
197
  /**
197
198
  * What a tree generated against this inventory may reference, the brand it is
198
- * held to, where the workspace keeps no reusable components or saved forms
199
+ * held to, where the workspace keeps no reusable components
199
200
  * (AGL-3030) that the page is built inline, the sections its plan names,
200
201
  * which a link may take a visitor to (AGL-3097), and the players its plan
201
202
  * lists for this page, the only ones it may embed (AGL-3433).
@@ -84,7 +84,7 @@ import { aiPlanItemCountViolations } from "./ai-job-plan-conformance.js";
84
84
  'You build a web page one section at a time, in the order a confirmed plan gives. Each answer is ONE section: call submit_section with a flat node map whose root is the document wrapper (componentId "div") holding exactly one Section (section).',
85
85
  'The first section is the top of the page and holds the page’s one h1, its title. Every later section opens with an h2, and headings below it step down one level at a time.',
86
86
  'Place what the section’s plan line names. A component the site has is placed as an instance, never drawn again: componentId "reusableInstance", props {"refId": "<component id>", "propValues": {…}} filling the props the inventory lists. A form the site has is a Form (form) whose formId is that form’s id. Another page of the site is linked by its page id, with a Page Link (muiScreenLink) or a Button (muiButton) whose screenId is that id.',
87
- 'Where the request says the site keeps no saved forms or reusable components, draw them in the section instead: a repeated item is written once, and a form is a Form (form) with no formId, a formName saying what it collects and a submitLabel, holding one Form Field (formField) for each answer with its fieldName, label and fieldType.',
87
+ 'Where the request says the site keeps no reusable components, draw a repeated item in the section instead, written once. A form is only ever a saved one placed by its formId: where the plan line names no form, the section draws no form and no form fields.',
88
88
  'Lay the section out with a Container, a Stack or a Grid ("container": true, "spacing": 3) of Grid items sized like "xs:12 md:4", and size nothing with a fixed width. Colors come from the theme’s palette tokens such as primary.main, spacing from the spacing scale, and type from the text variants.',
89
89
  'The page renders inside the site’s layout, so a section is never a header, navigation or footer.',
90
90
  'A picture is an Image (image) with alt text that says what it should show and no src, for the site owner to fill from the media library.',
@@ -149,7 +149,7 @@ import { aiPlanItemCountViolations } from "./ai-job-plan-conformance.js";
149
149
  */ export function aiPageRecordNote(template) {
150
150
  return `This page is drafted as the record template of "${template.name}". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields.`;
151
151
  }
152
- /** What a section request says where the workspace keeps no reusable components or saved forms. */ export const AI_PAGE_SECTION_INLINE_LINE = 'This site keeps no saved forms or reusable components: write a repeated item once, and draw a form as a Form holding its Form Fields.';
152
+ /** What a section request says where the workspace keeps no reusable components. */ export const AI_PAGE_SECTION_INLINE_LINE = 'This site keeps no reusable components: write a repeated item once.';
153
153
  /** How a repeated item is written once, in the words a request and a re-ask share. */ const AI_PAGE_SECTION_REPEAT_SHAPE = `put {{1}}, {{2}}… where its copies differ, and give its outermost node "${AI_REPEAT_KEY}", one list of values a copy, in that order, as [["Title 1", "Text 1"], ["Title 2", "Text 2"]]`;
154
154
  /**
155
155
  * How a repeated item is written once (AGL-3053): the item's subtree once,
@@ -307,7 +307,7 @@ function isRecord(value) {
307
307
  }
308
308
  /**
309
309
  * What a tree generated against this inventory may reference, the brand it is
310
- * held to, where the workspace keeps no reusable components or saved forms
310
+ * held to, where the workspace keeps no reusable components
311
311
  * (AGL-3030) that the page is built inline, the sections its plan names,
312
312
  * which a link may take a visitor to (AGL-3097), and the players its plan
313
313
  * lists for this page, the only ones it may embed (AGL-3433).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-sections.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 { REUSABLE_INSTANCE_COMPONENT_ID } from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n aiPlanCreateFor,\n type AiBuildPlanRecord,\n type AiBuildPlanScreen,\n type AiBuildPlanSection,\n} from '../model/ai-build-plan'\nimport { aiPageTypeDefinition, parseAiPageJobInputs } from '../model/ai-page-job'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport type { AiTool } from '../providers/contract'\nimport {\n AI_REASK_OFFENDING_MAX_CHARS,\n aiAnswerTree,\n aiNodeTreeContextFromInventory,\n type AiGenerationCheck,\n} from '../runtime/ai-doctrine'\nimport {\n detectCutLines,\n detectDisagreeingNodes,\n isAiLinkElement,\n validateAiDoctrineTree,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP, validateAiNodeTree } from '../runtime/ai-node-tree'\nimport { aiPageLinkTarget, aiPageScrollInteraction } from '../runtime/ai-page-links'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE_CATALOG } from '../runtime/ai-palette.generated'\nimport { AI_REPEAT_KEY, expandAiRepeatedItems } from '../runtime/ai-repeated-items'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiPlanItemCountViolations } from './ai-job-plan-conformance'\n\n/**\n * A page built one section at a time (AGL-2907): what one section pass asks\n * for, how its answer is held to the building rules as part of the page, and\n * how it joins the page.\n *\n * Pure: no Firestore and no provider, so the page step, its specs and the\n * evals read one definition of a section.\n *\n * ── Held as part of the page ─────────────────────────────────────────────\n *\n * A section is checked twice over, by validators the doctrine already has:\n * the palette validator on the section as the model wrote it, then the\n * doctrine's whole page check on the page built so far with the section\n * added. So the page's one main, its one h1 and its heading outline, repeats\n * across sections, and its weight against the page budget are held on every\n * pass, not only on the last. The violations name the section's own nodes,\n * by the ids the model wrote, so a re-ask quotes this section and nothing\n * else — the page is never sent back.\n *\n * ── A repeated item written once (AGL-3053) ──────────────────────────────\n *\n * Where the workspace keeps no reusable components, an answer writes a\n * repeated item once and lists its copies' values, and the check draws the\n * copies before either validator reads the section (`ai-repeated-items.ts`).\n * Both then read the section as the page stores it, so the page's rules hold\n * on every copy exactly as on an item written out in full, and a finding on a\n * copy names the node the model wrote. Where the workspace keeps components\n * the same shape is written around an instance (AGL-3024), so a section of\n * many cards costs its structure once on every plan; an item that places no\n * instance is still refused there.\n */\n\n/** The section root id a pass writes, from the job and the plan section's index. */\nexport function aiPageSectionNodeId(jobId: string, index: number): string {\n return `ai-${jobId.slice(0, 12)}-section-${index + 1}`\n}\n\n/** The strict tool a section arrives through. The node map rides as JSON text, as a page's does. */\nexport const AI_PAGE_SECTION_TOOL: AiTool = {\n name: 'submit_section',\n description: 'Submit one section of the page as one flat node map.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree'],\n properties: {\n tree: {\n type: 'string',\n description:\n 'The section as JSON: {\"rootId\": \"<id>\", \"nodes\": {\"<id>\": {\"componentId\": \"...\", \"props\": {...}, \"sx\": {...}, \"nodes\": [\"<child id>\", ...]}}}. The root is the document wrapper (componentId \"div\") holding one Section.',\n },\n },\n },\n}\n\n/** The page step's own instructions, cached after the doctrine, with the screen palette after them. */\nexport const AI_JOB_PAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build a web page one section at a time, in the order a confirmed plan gives. Each answer is ONE section: call submit_section with a flat node map whose root is the document wrapper (componentId \"div\") holding exactly one Section (section).',\n 'The first section is the top of the page and holds the page’s one h1, its title. Every later section opens with an h2, and headings below it step down one level at a time.',\n 'Place what the section’s plan line names. A component the site has is placed as an instance, never drawn again: componentId \"reusableInstance\", props {\"refId\": \"<component id>\", \"propValues\": {…}} filling the props the inventory lists. A form the site has is a Form (form) whose formId is that form’s id. Another page of the site is linked by its page id, with a Page Link (muiScreenLink) or a Button (muiButton) whose screenId is that id.',\n 'Where the request says the site keeps no saved forms or reusable components, draw them in the section instead: a repeated item is written once, and a form is a Form (form) with no formId, a formName saying what it collects and a submitLabel, holding one Form Field (formField) for each answer with its fieldName, label and fieldType.',\n 'Lay the section out with a Container, a Stack or a Grid (\"container\": true, \"spacing\": 3) of Grid items sized like \"xs:12 md:4\", and size nothing with a fixed width. Colors come from the theme’s palette tokens such as primary.main, spacing from the spacing scale, and type from the text variants.',\n 'The page renders inside the site’s layout, so a section is never a header, navigation or footer.',\n 'A picture is an Image (image) with alt text that says what it should show and no src, for the site owner to fill from the media library.',\n 'Write plain, specific copy in the site’s voice, with no filler. Where the brief leaves out a fact, such as a price, a phone number or an address, write the gap in square brackets instead of inventing it.',\n ].join('\\n'),\n },\n { text: AI_PALETTE_CATALOG.screen, cacheBreakpoint: true },\n]\n\n/** A plan reference as the request names it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/**\n * A record template as a page pass reads it (AGL-3475): the dataset by name,\n * its base, and the fields its copy binds, as `{{item.<id>}}` with the name a\n * member gave each. A dataset the plan creates has no ids yet, so its tokens\n * are the field names it was planned with.\n */\nexport interface AiPageRecordTemplate {\n /** The dataset: an inventory id, or the plan's `new:<name>`. */\n dataset: string\n name: string\n base: string\n fields: Array<{ id: string; name: string }>\n}\n\n/** The record template a plan screen is, read against the site's datasets; null for a page that is one page. */\nexport function aiPageRecordTemplate(\n record: AiBuildPlanRecord | null | undefined,\n inventory: AiSiteInventory | null,\n plan: AiJobPlan,\n): AiPageRecordTemplate | null {\n if (!record) return null\n const row = inventory?.datasets.find((entry) => entry.id === record.dataset)\n if (row) {\n return {\n dataset: record.dataset,\n name: row.name,\n base: record.base,\n fields: row.fields.map((name, index) => ({ id: row.fieldIds?.[index] || name, name })),\n }\n }\n const created = aiPlanCreateFor(plan, record.dataset)\n return {\n dataset: record.dataset,\n name: created?.name ?? plan.labels?.[record.dataset] ?? record.dataset,\n base: record.base,\n fields: (created?.fields ?? []).map((name) => ({ id: name, name })),\n }\n}\n\n/** Every token a record template's page may bind: each field, and `{{item.url}}`. */\nexport function aiPageRecordTokens(template: AiPageRecordTemplate | null): string[] {\n if (!template) return []\n return [...template.fields.map((field) => `{{item.${field.id}}}`), '{{item.url}}']\n}\n\n/** What a section request says on a record template's page. */\nexport function aiPageRecordLine(template: AiPageRecordTemplate): string {\n const fields = template.fields.length\n ? ` Its fields: ${template.fields\n .map((field) => (field.name === field.id ? `{{item.${field.id}}}` : `{{item.${field.id}}} (${field.name})`))\n .join(', ')}.`\n : ''\n return `This page is the record template of the dataset \"${template.name}\": it is served once per record, at /${template.base}/<record address>. Write what differs from one record to the next as {{item.<field>}}, never one record's copy, and write out what every record page shares.${fields}`\n}\n\n/**\n * What a record template's draft tells the member to do next. The binding is\n * a route, so the job never writes it (rule 13): the member saves it where\n * every record template is set up.\n */\nexport function aiPageRecordNote(template: AiPageRecordTemplate): string {\n return `This page is drafted as the record template of \"${template.name}\". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields.`\n}\n\nexport interface AiPageSectionPromptInput {\n job: Pick<AiJob, 'brief' | 'inputs'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n index: number\n /** The most elements the section may carry at this pass's answer ceiling. */\n maxElements: number\n /**\n * Whether the workspace keeps reusable components and saved forms\n * (AGL-3030); `false` asks for the section built inline. Absent is `true`.\n */\n reusableComponents?: boolean\n /** The dataset this page is the record template of; absent for a page that is one page. */\n record?: AiPageRecordTemplate | null\n}\n\n/** What a section request says where the workspace keeps no reusable components or saved forms. */\nexport const AI_PAGE_SECTION_INLINE_LINE =\n 'This site keeps no saved forms or reusable components: write a repeated item once, and draw a form as a Form holding its Form Fields.'\n\n/** How a repeated item is written once, in the words a request and a re-ask share. */\nconst AI_PAGE_SECTION_REPEAT_SHAPE = `put {{1}}, {{2}}… where its copies differ, and give its outermost node \"${AI_REPEAT_KEY}\", one list of values a copy, in that order, as [[\"Title 1\", \"Text 1\"], [\"Title 2\", \"Text 2\"]]`\n\n/**\n * How a repeated item is written once (AGL-3053): the item's subtree once,\n * with placeholders where its copies differ and their values listed, which\n * the section check draws into its copies (`ai-repeated-items.ts`). It rides\n * the request of a section whose plan line shows items on a workspace that\n * keeps no reusable components, and no other: a section with nothing to repeat\n * pays nothing for it, and the cached prefix every workspace shares says only\n * that a repeated item is written once.\n */\nexport const AI_PAGE_SECTION_REPEAT_LINE = `Write a repeated item once: ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n\n/**\n * How a workspace that keeps reusable components writes a repeated item\n * (AGL-3143 §10): the shape a Free section writes, around an instance of a\n * component the section already places — the instance ONCE, `{{n}}` through\n * the values it fills, and one list of values a copy — rather than the item's\n * subtree drawn once per copy, or one instance node written per copy.\n *\n * Six cards written as six instance nodes spend their structure six times;\n * written once they spend it once, which is the difference between a section\n * that fits its ceiling and one that is cut off. A section stays ONE section\n * either way: splitting it would give the page a second h2 for the same idea,\n * which is not what the member asked for and not what its outline should say.\n *\n * ⛔ The repeated item is the GRID ITEM, not the instance. Measured live on\n * 2026-09-22: told only to \"place ONE instance … and give its outermost node\n * repeat\", the model made the instance itself the repeated node and hung it\n * straight off the Grid container. The section fitted — the ceiling failure\n * was gone — and the page was then refused by rule 12, which requires every\n * child of a Grid container to be a sized Grid item (AGL-3055). The line has\n * to name the wrapper, because \"outermost node\" reads as the instance when\n * the instance is all the request mentions.\n *\n * It rides the request of a section whose plan line shows items AND names a\n * record to place, on the same terms the repeat line rides a Free one: a\n * section with nothing to repeat, or nothing to repeat it FROM, pays nothing\n * for it.\n *\n * ## Why a paid section could not be built without it (AGL-3024)\n *\n * The Free path is reconciled at both ends — its plan is held to the sections\n * its wall pays for (AGL-3070) and its request is shown the repeat shape\n * (AGL-3053). The paid path was reconciled at neither: `AI_BUILD_PLAN_LIMITS`\n * lets a plan promise up to `items: 500` in one section, the pass that builds\n * it fits `aiJobPageSectionMaxElements` — fifteen at the balanced tier — and\n * the request said only how many elements to keep under, never how to get\n * there. A six-card grid measured live on 2026-09-21 was drawn inline, ran\n * past its ceiling, and was cut off; the re-ask then said to place an instance\n * \"of a component the site has\" without naming one, so it was cut off too, and\n * `AI_GENERATION_MAX_ATTEMPTS` ended the job with no page. A four-card grid on\n * the same site had fitted, which is why this went unseen. Naming the shape\n * was not enough on its own: `ai-repeated-items.ts` refused `repeat` outright\n * on such a workspace, so the only shape the request could ask for was the\n * expensive one.\n *\n * ⛔ This does NOT make the plan and the pass agree about what fits — a plan\n * may still promise more items than any ceiling can carry, and this line only\n * gives the pass its cheapest way to try. That bound belongs on the plan step\n * and is deliberately not taken here.\n */\nexport const AI_PAGE_SECTION_INSTANCE_LINE = `Write a repeated item once: put ONE instance of the component this section places inside ONE Grid item sized like \"xs:12 sm:6 md:4\", and repeat THAT GRID ITEM rather than the instance — every child of a Grid container is itself a Grid item. Put {{1}}, {{2}}… where the copies differ, and give the Grid item \"${AI_REPEAT_KEY}\", one list of values a copy, in that order, as [[\"Title 1\", \"Text 1\"], [\"Title 2\", \"Text 2\"]].`\n\n/**\n * What a pass whose answer ran past its ceiling is told makes a section\n * smaller (AGL-3042): fewer elements, down to the budget the request gave,\n * shorter copy, and a repeated item drawn no more than once — placed as an\n * instance where the workspace keeps reusable components, and written once\n * where it keeps none (AGL-3053), with the shape spelled out, since a section\n * whose plan line showed no items was never told it. It rides the re-ask,\n * never the first request, so a pass that fits sends no byte more.\n */\nexport function aiPageSectionSmaller(input: Pick<AiPageSectionPromptInput, 'maxElements' | 'reusableComponents'>): string {\n return input.reusableComponents === false\n ? `Make it smaller: use fewer elements, at most ${input.maxElements}; write shorter copy; and write a repeated item once instead of drawing it again: ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n : `Make it smaller: use fewer elements, at most ${input.maxElements}; write shorter copy; and write a repeated item once instead of drawing it again: put ONE instance of a component the site has inside ONE Grid item sized like \"xs:12 sm:6 md:4\", and repeat that Grid item — ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n}\n\n/** One pass's user turn: the page, the brief, the plan, this section and what is built above it. */\nexport function aiPageSectionPrompt(input: AiPageSectionPromptInput): string {\n const { job, plan, screen, index } = input\n const inputs = parseAiPageJobInputs(job.inputs)\n const type = typeof inputs === 'string' ? null : aiPageTypeDefinition(inputs.pageType)\n const section = screen.sections[index] as AiBuildPlanSection\n const places = section.uses.length\n ? ` It places ${section.uses.map((ref) => referenceOf(plan, ref)).join(', ')}.`\n : ''\n const items = section.items ? ` It shows ${section.items} items.` : ''\n const built = screen.sections.slice(0, index).map((entry, position) => `${position + 1}. ${entry.name}`)\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n ...(type ? [`Page type: ${type.purpose}`] : []),\n ...(input.record ? [aiPageRecordLine(input.record)] : []),\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n `Build section ${index + 1} of ${screen.sections.length}: \"${section.name}\".${places}${items}`,\n built.length\n ? `Already built, above it: ${built.join('; ')}. The page’s h1 is in section 1.`\n : 'Nothing is built yet: this section holds the page’s h1.',\n ...(input.reusableComponents === false\n ? [AI_PAGE_SECTION_INLINE_LINE, ...(section.items ? [AI_PAGE_SECTION_REPEAT_LINE] : [])]\n : section.items && section.uses.length\n ? [AI_PAGE_SECTION_INSTANCE_LINE]\n : []),\n `Keep this section to at most ${input.maxElements} elements.`,\n ].join('\\n')\n}\n\n/** A validated section: its nodes keyed as the page stores them, its root under the page root. */\nexport interface AiPageSection {\n rootId: string\n nodes: NodesMap\n /** The page's weight with the section added, as the doctrine measured it. */\n load: AiLoadEstimate | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A page with nothing on it yet: the document wrapper. */\nexport function aiEmptyPage(): NodesMap {\n return {\n [CANVAS_ROOT_ELEMENT_ID]: {\n $id: CANVAS_ROOT_ELEMENT_ID,\n componentId: 'div',\n parentId: null,\n nodes: [],\n },\n } as unknown as NodesMap\n}\n\n/**\n * The page with a section in its plan position: after every section with a\n * lower index and before every one with a higher, the page's other children\n * — whatever a member placed — kept where they are. A section already on the\n * page is replaced, never doubled.\n */\nexport function aiPageWithSection(\n page: NodesMap,\n section: AiPageSection,\n sectionIds: readonly string[],\n): NodesMap {\n const nodes: Record<string, Record<string, unknown>> = {}\n const previous = (page as unknown as Record<string, Record<string, unknown>>)[section.rootId]\n const replaced = new Set<string>()\n if (previous) {\n for (const { id } of walkTree({\n rootId: section.rootId,\n nodes: page as unknown as Record<string, AiDoctrineNode>,\n })) {\n replaced.add(id)\n }\n }\n for (const [id, node] of Object.entries(page)) {\n if (!replaced.has(id)) nodes[id] = { ...(node as unknown as Record<string, unknown>) }\n }\n const root = nodes[CANVAS_ROOT_ELEMENT_ID] ?? (aiEmptyPage()[CANVAS_ROOT_ELEMENT_ID] as unknown as Record<string, unknown>)\n const children = (Array.isArray(root['nodes']) ? (root['nodes'] as string[]) : []).filter(\n (id) => id !== section.rootId,\n )\n const order = new Map(sectionIds.map((id, position) => [id, position]))\n const at = order.get(section.rootId) ?? sectionIds.length\n const before = children.findIndex((id) => (order.get(id) ?? -1) > at)\n children.splice(before === -1 ? children.length : before, 0, section.rootId)\n nodes[CANVAS_ROOT_ELEMENT_ID] = { ...root, nodes: children }\n for (const [id, node] of Object.entries(section.nodes)) {\n nodes[id] = { ...(node as unknown as Record<string, unknown>) }\n }\n return nodes as unknown as NodesMap\n}\n\n/**\n * What a tree generated against this inventory may reference, the brand it is\n * held to, where the workspace keeps no reusable components or saved forms\n * (AGL-3030) that the page is built inline, the sections its plan names,\n * which a link may take a visitor to (AGL-3097), and the players its plan\n * lists for this page, the only ones it may embed (AGL-3433).\n */\nexport function aiPageCheckContext(\n inventory: AiSiteInventory | null,\n options: {\n reusableComponents?: boolean\n sections?: readonly string[]\n embeds?: AiDoctrineTreeContext['plannedEmbeds']\n /** A record template's tokens, which its links and images may name (AGL-3475). */\n recordTokens?: readonly string[]\n } = {},\n): AiDoctrineTreeContext {\n return {\n brand: inventory?.theme ? { colors: inventory.theme.colors, fonts: inventory.theme.fonts } : null,\n ...aiNodeTreeContextFromInventory(inventory),\n homeScreenIds: aiHomeScreenIds(inventory),\n ...(options.reusableComponents === false ? { reusableComponents: false } : {}),\n ...(options.sections?.length ? { pageSections: options.sections } : {}),\n ...(options.embeds?.length ? { plannedEmbeds: options.embeds } : {}),\n ...(options.recordTokens?.length ? { bindingTokens: options.recordTokens } : {}),\n }\n}\n\nexport interface AiPageSectionCheckInput {\n /** The page as stored so far; an empty page on the first pass. */\n page: NodesMap\n /** Every section root id of the plan, in order. */\n sectionIds: readonly string[]\n index: number\n context: AiDoctrineTreeContext\n /**\n * The plan line this pass answers for: what it places by inventory id, and\n * how many items it promised, which the answer is held to (AGL-3024).\n */\n section: Pick<AiBuildPlanSection, 'name' | 'uses' | 'items'>\n inventory: AiSiteInventory | null\n}\n\n/** The parts of an answer the violations name, as the model wrote them, capped as the doctrine caps a re-ask. */\nfunction offendingOf(raw: unknown, violations: readonly AiDoctrineViolation[]): { offending?: Record<string, unknown> } {\n const rawNodes = isRecord(raw) && isRecord(raw['nodes']) ? raw['nodes'] : {}\n const offending: Record<string, unknown> = {}\n let used = 2\n for (const id of new Set(violations.flatMap((violation) => violation.nodeIds ?? []))) {\n if (rawNodes[id] === undefined) continue\n const size = JSON.stringify(rawNodes[id]).length + id.length + 4\n if (used + size > AI_REASK_OFFENDING_MAX_CHARS) break\n offending[id] = rawNodes[id]\n used += size\n }\n return Object.keys(offending).length ? { offending } : {}\n}\n\n/**\n * The check a section's answer is held to: a repeated item written once drawn\n * into its copies (AGL-3053), then the palette validator on the section, with\n * any line it cut at its ceiling refused (AGL-3076), the doctrine's page check\n * on the page with the section added, and the plan line — every component it\n * names placed as an instance, every form bound by its id (rule 7), and as\n * many items as it promised (AGL-3024). Violations\n * name the section's own nodes by the ids the MODEL wrote, and a copy's nodes\n * by the item's; one that names only nodes an earlier pass stored belongs to\n * the page rather than to this answer, and is left to the last pass (AGL-3078).\n */\nexport function aiPageSectionCheck(input: AiPageSectionCheckInput): AiGenerationCheck<AiPageSection> {\n const sectionId = input.sectionIds[input.index]\n const components = new Set((input.inventory?.components ?? []).map((row) => row.id))\n const forms = new Set((input.inventory?.forms ?? []).map((row) => row.id))\n return (answer) => {\n const raw = aiAnswerTree(answer)\n // Before anything is drawn or dropped: a repeated item written and never\n // placed is content the section loses silently, and a child named and\n // never written ends the pass with nothing to answer (AGL-3143).\n const disagreeing = detectDisagreeingNodes(raw)\n if (disagreeing.length) {\n return { value: null, violations: disagreeing, ...offendingOf(raw, disagreeing) }\n }\n const drawn = expandAiRepeatedItems(raw, { inline: input.context.reusableComponents === false, noun: 'section' })\n if (drawn.ok === false) {\n return { value: null, violations: drawn.violations, ...offendingOf(raw, drawn.violations) }\n }\n // Every id below is the drawn tree's; a copy's leads back to the node the model wrote.\n const written = (id: string): string => drawn.sourceIds[id] ?? id\n const validated = validateAiNodeTree(drawn.tree, 'screen', input.context)\n if (validated.ok === false) {\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: `tree-${validated.code}`,\n message: 'The answer could not be used as a section.',\n detail: validated.error,\n },\n ],\n }\n }\n const wrapper = validated.nodes[validated.rootId] as unknown as AiDoctrineNode | undefined\n const top = (wrapper?.nodes ?? []).filter((id) => validated.nodes[id])\n const sectionNode = top.length === 1 ? (validated.nodes[top[0]] as unknown as AiDoctrineNode) : null\n if (!sectionNode || sectionNode.componentId !== 'section') {\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'section-shape',\n message: 'Answer with exactly one Section element inside the document wrapper.',\n },\n ],\n }\n }\n\n // The section's root takes its plan id, so a pass run again finds it, and\n // hangs under the page root; every other minted id stays as minted.\n const minted = top[0]\n const modelIds: Record<string, string> = Object.fromEntries(\n Object.entries(validated.sourceIds).map(([id, source]) => [id, written(source)]),\n )\n modelIds[sectionId] = written(validated.sourceIds[minted] ?? minted)\n const sectionNodes: Record<string, Record<string, unknown>> = {}\n for (const { id, node } of walkTree({\n rootId: minted,\n nodes: validated.nodes as unknown as Record<string, AiDoctrineNode>,\n })) {\n const stored = { ...(node as unknown as Record<string, unknown>) }\n if (id === minted) {\n stored['$id'] = sectionId\n stored['parentId'] = CANVAS_ROOT_ELEMENT_ID\n } else if (stored['parentId'] === minted) {\n stored['parentId'] = sectionId\n }\n sectionNodes[id === minted ? sectionId : id] = stored\n }\n const drawnNodes = isRecord(drawn.tree) && isRecord(drawn.tree['nodes']) ? drawn.tree['nodes'] : {}\n // What the model wrote of one of the section's nodes, by the id the page stores it under.\n const drawnIdOf = (stored: string): string =>\n validated.sourceIds[stored === sectionId ? minted : stored] ?? stored\n // A link that names a section of this page carries the platform's Scroll to\n // element interaction to that section's root, whose id is minted from the\n // plan before the section is built (AGL-3097).\n const sections = input.context.pageSections ?? []\n for (const [id, node] of Object.entries(sectionNodes)) {\n const props = isRecord(node['props']) ? node['props'] : {}\n if (!isAiLinkElement(node['componentId']) || props['screenId'] || props['href']) continue\n const target = aiPageLinkTarget(drawnNodes[drawnIdOf(id)], sections)\n if (target?.kind === 'section') {\n node['interactions'] = [aiPageScrollInteraction(input.sectionIds[target.section], sections[target.section])]\n }\n }\n const section: AiPageSection = { rootId: sectionId, nodes: sectionNodes as unknown as NodesMap, load: null }\n\n const page = aiPageWithSection(input.page, section, input.sectionIds)\n const own = new Set(Object.keys(sectionNodes))\n // The page check reads the section as the page stores it, where the palette\n // validator has already dropped what it could not read, such as a\n // `container` written as text or a link's `scrollTo`; what the model wrote\n // of each of the section's nodes is read from the section as it was drawn\n // (AGL-3078). A link the page already carries to one of its section roots\n // goes there, whichever pass wrote it (AGL-3097).\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...input.context,\n writtenNode: (id) => (own.has(id) ? drawnNodes[drawnIdOf(id)] : undefined),\n scrollTargetIds: input.sectionIds,\n })\n // The page check mints its ids afresh: its map leads back to the page's\n // ids, and the section's own lead back to the model's.\n const pageIds = report.tree?.sourceIds ?? {}\n const toModel = (id: string): string | null => {\n const stored = pageIds[id] ?? id\n return own.has(stored) ? (modelIds[stored] ?? stored) : null\n }\n // The page check reads the section as it is stored, where a line the\n // palette validator cut is already cut, so the cut is read from the\n // section's own validation, first (AGL-3076). Copies of one item name one\n // node the model wrote, once.\n //\n // A PASS ANSWERS ONLY FOR THE SECTION IT WROTE (AGL-3078). The page check\n // reads the whole page, so it also names nodes an earlier pass stored; the\n // model is not shown those and cannot mend them, so a finding that names\n // none of this section's nodes is not held against this answer. Dropping\n // it neither hides nor forgives it: the last pass runs the same check on\n // the finished page, where every node is the page's and the review names\n // each one and outlines it. Kept, such a finding would re-ask the model\n // for a node it never saw, refuse the same answer twice, and stop the page\n // with findings whose ids all filtered away — no node ids, and so no\n // outline either.\n const violations: AiDoctrineViolation[] = [\n ...detectCutLines(validated.repairs, drawnNodes, written),\n ...report.violations.flatMap((violation) => {\n if (!violation.nodeIds?.length) return [violation]\n const named = [...new Set(violation.nodeIds.map(toModel).filter((id): id is string => id !== null))]\n return named.length ? [{ ...violation, nodeIds: named }] : []\n }),\n ]\n\n const placed = new Set<string>()\n const bound = new Set<string>()\n for (const node of Object.values(sectionNodes)) {\n const props = isRecord(node['props']) ? node['props'] : {}\n if (node['componentId'] === REUSABLE_INSTANCE_COMPONENT_ID && typeof props[AI_INSTANCE_REF_PROP] === 'string') {\n placed.add(props[AI_INSTANCE_REF_PROP] as string)\n }\n if (node['componentId'] === 'form' && typeof props['formId'] === 'string') bound.add(props['formId'])\n }\n const missing = input.section.uses.filter(\n (ref) => (components.has(ref) && !placed.has(ref)) || (forms.has(ref) && !bound.has(ref)),\n )\n if (missing.length) {\n violations.push({\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan places ${missing.join(', ')} in this section, and the section does not. Place each one by its id.`,\n })\n }\n // The count the plan promised, against the section as the page stores it\n // (AGL-3024): the prompt states it, and until now nothing read it back.\n violations.push(\n ...aiPlanItemCountViolations(input.section, {\n rootId: sectionId,\n nodes: sectionNodes as unknown as Record<string, AiDoctrineNode>,\n }),\n )\n\n return {\n value: { ...section, load: report.load },\n violations,\n ...offendingOf(raw, violations),\n }\n }\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","CANVAS_ROOT_ELEMENT_ID","aiPlanCreateFor","aiPageTypeDefinition","parseAiPageJobInputs","aiHomeScreenIds","AI_REASK_OFFENDING_MAX_CHARS","aiAnswerTree","aiNodeTreeContextFromInventory","detectCutLines","detectDisagreeingNodes","isAiLinkElement","validateAiDoctrineTree","walkTree","AI_INSTANCE_REF_PROP","validateAiNodeTree","aiPageLinkTarget","aiPageScrollInteraction","AI_PALETTE_CATALOG","AI_REPEAT_KEY","expandAiRepeatedItems","aiJobBriefLine","aiPlanReferenceLines","aiPlanItemCountViolations","aiPageSectionNodeId","jobId","index","slice","AI_PAGE_SECTION_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","AI_JOB_PAGE_INSTRUCTIONS","text","join","screen","cacheBreakpoint","referenceOf","plan","ref","label","labels","aiPageRecordTemplate","record","inventory","created","row","datasets","find","entry","id","dataset","base","fields","map","fieldIds","aiPageRecordTokens","template","field","aiPageRecordLine","length","aiPageRecordNote","AI_PAGE_SECTION_INLINE_LINE","AI_PAGE_SECTION_REPEAT_SHAPE","AI_PAGE_SECTION_REPEAT_LINE","AI_PAGE_SECTION_INSTANCE_LINE","aiPageSectionSmaller","input","reusableComponents","maxElements","aiPageSectionPrompt","job","inputs","pageType","section","sections","places","uses","items","built","position","title","slug","purpose","isRecord","value","Array","isArray","aiEmptyPage","$id","componentId","parentId","nodes","aiPageWithSection","page","sectionIds","order","previous","rootId","replaced","Set","add","node","Object","entries","has","root","children","filter","Map","at","get","before","findIndex","splice","aiPageCheckContext","options","brand","theme","colors","fonts","homeScreenIds","pageSections","embeds","plannedEmbeds","recordTokens","bindingTokens","offendingOf","raw","violations","rawNodes","offending","used","flatMap","violation","nodeIds","undefined","size","JSON","stringify","keys","aiPageSectionCheck","sectionId","components","forms","answer","validated","report","disagreeing","drawn","inline","context","noun","ok","written","sourceIds","rule","code","message","detail","error","wrapper","top","sectionNode","minted","modelIds","fromEntries","source","sectionNodes","stored","drawnNodes","drawnIdOf","props","target","kind","load","own","writtenNode","scrollTargetIds","pageIds","toModel","repairs","named","placed","bound","values","missing","push"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-sections.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 { REUSABLE_INSTANCE_COMPONENT_ID } from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n aiPlanCreateFor,\n type AiBuildPlanRecord,\n type AiBuildPlanScreen,\n type AiBuildPlanSection,\n} from '../model/ai-build-plan'\nimport { aiPageTypeDefinition, parseAiPageJobInputs } from '../model/ai-page-job'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport type { AiTool } from '../providers/contract'\nimport {\n AI_REASK_OFFENDING_MAX_CHARS,\n aiAnswerTree,\n aiNodeTreeContextFromInventory,\n type AiGenerationCheck,\n} from '../runtime/ai-doctrine'\nimport {\n detectCutLines,\n detectDisagreeingNodes,\n isAiLinkElement,\n validateAiDoctrineTree,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_INSTANCE_REF_PROP, validateAiNodeTree } from '../runtime/ai-node-tree'\nimport { aiPageLinkTarget, aiPageScrollInteraction } from '../runtime/ai-page-links'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { AI_PALETTE_CATALOG } from '../runtime/ai-palette.generated'\nimport { AI_REPEAT_KEY, expandAiRepeatedItems } from '../runtime/ai-repeated-items'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiPlanItemCountViolations } from './ai-job-plan-conformance'\n\n/**\n * A page built one section at a time (AGL-2907): what one section pass asks\n * for, how its answer is held to the building rules as part of the page, and\n * how it joins the page.\n *\n * Pure: no Firestore and no provider, so the page step, its specs and the\n * evals read one definition of a section.\n *\n * ── Held as part of the page ─────────────────────────────────────────────\n *\n * A section is checked twice over, by validators the doctrine already has:\n * the palette validator on the section as the model wrote it, then the\n * doctrine's whole page check on the page built so far with the section\n * added. So the page's one main, its one h1 and its heading outline, repeats\n * across sections, and its weight against the page budget are held on every\n * pass, not only on the last. The violations name the section's own nodes,\n * by the ids the model wrote, so a re-ask quotes this section and nothing\n * else — the page is never sent back.\n *\n * ── A repeated item written once (AGL-3053) ──────────────────────────────\n *\n * Where the workspace keeps no reusable components, an answer writes a\n * repeated item once and lists its copies' values, and the check draws the\n * copies before either validator reads the section (`ai-repeated-items.ts`).\n * Both then read the section as the page stores it, so the page's rules hold\n * on every copy exactly as on an item written out in full, and a finding on a\n * copy names the node the model wrote. Where the workspace keeps components\n * the same shape is written around an instance (AGL-3024), so a section of\n * many cards costs its structure once on every plan; an item that places no\n * instance is still refused there.\n */\n\n/** The section root id a pass writes, from the job and the plan section's index. */\nexport function aiPageSectionNodeId(jobId: string, index: number): string {\n return `ai-${jobId.slice(0, 12)}-section-${index + 1}`\n}\n\n/** The strict tool a section arrives through. The node map rides as JSON text, as a page's does. */\nexport const AI_PAGE_SECTION_TOOL: AiTool = {\n name: 'submit_section',\n description: 'Submit one section of the page as one flat node map.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree'],\n properties: {\n tree: {\n type: 'string',\n description:\n 'The section as JSON: {\"rootId\": \"<id>\", \"nodes\": {\"<id>\": {\"componentId\": \"...\", \"props\": {...}, \"sx\": {...}, \"nodes\": [\"<child id>\", ...]}}}. The root is the document wrapper (componentId \"div\") holding one Section.',\n },\n },\n },\n}\n\n/** The page step's own instructions, cached after the doctrine, with the screen palette after them. */\nexport const AI_JOB_PAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build a web page one section at a time, in the order a confirmed plan gives. Each answer is ONE section: call submit_section with a flat node map whose root is the document wrapper (componentId \"div\") holding exactly one Section (section).',\n 'The first section is the top of the page and holds the page’s one h1, its title. Every later section opens with an h2, and headings below it step down one level at a time.',\n 'Place what the section’s plan line names. A component the site has is placed as an instance, never drawn again: componentId \"reusableInstance\", props {\"refId\": \"<component id>\", \"propValues\": {…}} filling the props the inventory lists. A form the site has is a Form (form) whose formId is that form’s id. Another page of the site is linked by its page id, with a Page Link (muiScreenLink) or a Button (muiButton) whose screenId is that id.',\n 'Where the request says the site keeps no reusable components, draw a repeated item in the section instead, written once. A form is only ever a saved one placed by its formId: where the plan line names no form, the section draws no form and no form fields.',\n 'Lay the section out with a Container, a Stack or a Grid (\"container\": true, \"spacing\": 3) of Grid items sized like \"xs:12 md:4\", and size nothing with a fixed width. Colors come from the theme’s palette tokens such as primary.main, spacing from the spacing scale, and type from the text variants.',\n 'The page renders inside the site’s layout, so a section is never a header, navigation or footer.',\n 'A picture is an Image (image) with alt text that says what it should show and no src, for the site owner to fill from the media library.',\n 'Write plain, specific copy in the site’s voice, with no filler. Where the brief leaves out a fact, such as a price, a phone number or an address, write the gap in square brackets instead of inventing it.',\n ].join('\\n'),\n },\n { text: AI_PALETTE_CATALOG.screen, cacheBreakpoint: true },\n]\n\n/** A plan reference as the request names it: the id with the name it was planned under. */\nfunction referenceOf(plan: AiJobPlan, ref: string): string {\n const label = plan.labels?.[ref]\n return label ? `${ref} (\"${label}\")` : ref\n}\n\n/**\n * A record template as a page pass reads it (AGL-3475): the dataset by name,\n * its base, and the fields its copy binds, as `{{item.<id>}}` with the name a\n * member gave each. A dataset the plan creates has no ids yet, so its tokens\n * are the field names it was planned with.\n */\nexport interface AiPageRecordTemplate {\n /** The dataset: an inventory id, or the plan's `new:<name>`. */\n dataset: string\n name: string\n base: string\n fields: Array<{ id: string; name: string }>\n}\n\n/** The record template a plan screen is, read against the site's datasets; null for a page that is one page. */\nexport function aiPageRecordTemplate(\n record: AiBuildPlanRecord | null | undefined,\n inventory: AiSiteInventory | null,\n plan: AiJobPlan,\n): AiPageRecordTemplate | null {\n if (!record) return null\n const row = inventory?.datasets.find((entry) => entry.id === record.dataset)\n if (row) {\n return {\n dataset: record.dataset,\n name: row.name,\n base: record.base,\n fields: row.fields.map((name, index) => ({ id: row.fieldIds?.[index] || name, name })),\n }\n }\n const created = aiPlanCreateFor(plan, record.dataset)\n return {\n dataset: record.dataset,\n name: created?.name ?? plan.labels?.[record.dataset] ?? record.dataset,\n base: record.base,\n fields: (created?.fields ?? []).map((name) => ({ id: name, name })),\n }\n}\n\n/** Every token a record template's page may bind: each field, and `{{item.url}}`. */\nexport function aiPageRecordTokens(template: AiPageRecordTemplate | null): string[] {\n if (!template) return []\n return [...template.fields.map((field) => `{{item.${field.id}}}`), '{{item.url}}']\n}\n\n/** What a section request says on a record template's page. */\nexport function aiPageRecordLine(template: AiPageRecordTemplate): string {\n const fields = template.fields.length\n ? ` Its fields: ${template.fields\n .map((field) => (field.name === field.id ? `{{item.${field.id}}}` : `{{item.${field.id}}} (${field.name})`))\n .join(', ')}.`\n : ''\n return `This page is the record template of the dataset \"${template.name}\": it is served once per record, at /${template.base}/<record address>. Write what differs from one record to the next as {{item.<field>}}, never one record's copy, and write out what every record page shares.${fields}`\n}\n\n/**\n * What a record template's draft tells the member to do next. The binding is\n * a route, so the job never writes it (rule 13): the member saves it where\n * every record template is set up.\n */\nexport function aiPageRecordNote(template: AiPageRecordTemplate): string {\n return `This page is drafted as the record template of \"${template.name}\". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields.`\n}\n\nexport interface AiPageSectionPromptInput {\n job: Pick<AiJob, 'brief' | 'inputs'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n index: number\n /** The most elements the section may carry at this pass's answer ceiling. */\n maxElements: number\n /**\n * Whether the workspace keeps reusable components (AGL-3030); `false`\n * asks for a repeated item drawn in the section. Absent is `true`. A form\n * is never drawn inline, whatever this says (AGL-3596).\n */\n reusableComponents?: boolean\n /** The dataset this page is the record template of; absent for a page that is one page. */\n record?: AiPageRecordTemplate | null\n}\n\n/** What a section request says where the workspace keeps no reusable components. */\nexport const AI_PAGE_SECTION_INLINE_LINE =\n 'This site keeps no reusable components: write a repeated item once.'\n\n/** How a repeated item is written once, in the words a request and a re-ask share. */\nconst AI_PAGE_SECTION_REPEAT_SHAPE = `put {{1}}, {{2}}… where its copies differ, and give its outermost node \"${AI_REPEAT_KEY}\", one list of values a copy, in that order, as [[\"Title 1\", \"Text 1\"], [\"Title 2\", \"Text 2\"]]`\n\n/**\n * How a repeated item is written once (AGL-3053): the item's subtree once,\n * with placeholders where its copies differ and their values listed, which\n * the section check draws into its copies (`ai-repeated-items.ts`). It rides\n * the request of a section whose plan line shows items on a workspace that\n * keeps no reusable components, and no other: a section with nothing to repeat\n * pays nothing for it, and the cached prefix every workspace shares says only\n * that a repeated item is written once.\n */\nexport const AI_PAGE_SECTION_REPEAT_LINE = `Write a repeated item once: ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n\n/**\n * How a workspace that keeps reusable components writes a repeated item\n * (AGL-3143 §10): the shape a Free section writes, around an instance of a\n * component the section already places — the instance ONCE, `{{n}}` through\n * the values it fills, and one list of values a copy — rather than the item's\n * subtree drawn once per copy, or one instance node written per copy.\n *\n * Six cards written as six instance nodes spend their structure six times;\n * written once they spend it once, which is the difference between a section\n * that fits its ceiling and one that is cut off. A section stays ONE section\n * either way: splitting it would give the page a second h2 for the same idea,\n * which is not what the member asked for and not what its outline should say.\n *\n * ⛔ The repeated item is the GRID ITEM, not the instance. Measured live on\n * 2026-09-22: told only to \"place ONE instance … and give its outermost node\n * repeat\", the model made the instance itself the repeated node and hung it\n * straight off the Grid container. The section fitted — the ceiling failure\n * was gone — and the page was then refused by rule 12, which requires every\n * child of a Grid container to be a sized Grid item (AGL-3055). The line has\n * to name the wrapper, because \"outermost node\" reads as the instance when\n * the instance is all the request mentions.\n *\n * It rides the request of a section whose plan line shows items AND names a\n * record to place, on the same terms the repeat line rides a Free one: a\n * section with nothing to repeat, or nothing to repeat it FROM, pays nothing\n * for it.\n *\n * ## Why a paid section could not be built without it (AGL-3024)\n *\n * The Free path is reconciled at both ends — its plan is held to the sections\n * its wall pays for (AGL-3070) and its request is shown the repeat shape\n * (AGL-3053). The paid path was reconciled at neither: `AI_BUILD_PLAN_LIMITS`\n * lets a plan promise up to `items: 500` in one section, the pass that builds\n * it fits `aiJobPageSectionMaxElements` — fifteen at the balanced tier — and\n * the request said only how many elements to keep under, never how to get\n * there. A six-card grid measured live on 2026-09-21 was drawn inline, ran\n * past its ceiling, and was cut off; the re-ask then said to place an instance\n * \"of a component the site has\" without naming one, so it was cut off too, and\n * `AI_GENERATION_MAX_ATTEMPTS` ended the job with no page. A four-card grid on\n * the same site had fitted, which is why this went unseen. Naming the shape\n * was not enough on its own: `ai-repeated-items.ts` refused `repeat` outright\n * on such a workspace, so the only shape the request could ask for was the\n * expensive one.\n *\n * ⛔ This does NOT make the plan and the pass agree about what fits — a plan\n * may still promise more items than any ceiling can carry, and this line only\n * gives the pass its cheapest way to try. That bound belongs on the plan step\n * and is deliberately not taken here.\n */\nexport const AI_PAGE_SECTION_INSTANCE_LINE = `Write a repeated item once: put ONE instance of the component this section places inside ONE Grid item sized like \"xs:12 sm:6 md:4\", and repeat THAT GRID ITEM rather than the instance — every child of a Grid container is itself a Grid item. Put {{1}}, {{2}}… where the copies differ, and give the Grid item \"${AI_REPEAT_KEY}\", one list of values a copy, in that order, as [[\"Title 1\", \"Text 1\"], [\"Title 2\", \"Text 2\"]].`\n\n/**\n * What a pass whose answer ran past its ceiling is told makes a section\n * smaller (AGL-3042): fewer elements, down to the budget the request gave,\n * shorter copy, and a repeated item drawn no more than once — placed as an\n * instance where the workspace keeps reusable components, and written once\n * where it keeps none (AGL-3053), with the shape spelled out, since a section\n * whose plan line showed no items was never told it. It rides the re-ask,\n * never the first request, so a pass that fits sends no byte more.\n */\nexport function aiPageSectionSmaller(input: Pick<AiPageSectionPromptInput, 'maxElements' | 'reusableComponents'>): string {\n return input.reusableComponents === false\n ? `Make it smaller: use fewer elements, at most ${input.maxElements}; write shorter copy; and write a repeated item once instead of drawing it again: ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n : `Make it smaller: use fewer elements, at most ${input.maxElements}; write shorter copy; and write a repeated item once instead of drawing it again: put ONE instance of a component the site has inside ONE Grid item sized like \"xs:12 sm:6 md:4\", and repeat that Grid item — ${AI_PAGE_SECTION_REPEAT_SHAPE}.`\n}\n\n/** One pass's user turn: the page, the brief, the plan, this section and what is built above it. */\nexport function aiPageSectionPrompt(input: AiPageSectionPromptInput): string {\n const { job, plan, screen, index } = input\n const inputs = parseAiPageJobInputs(job.inputs)\n const type = typeof inputs === 'string' ? null : aiPageTypeDefinition(inputs.pageType)\n const section = screen.sections[index] as AiBuildPlanSection\n const places = section.uses.length\n ? ` It places ${section.uses.map((ref) => referenceOf(plan, ref)).join(', ')}.`\n : ''\n const items = section.items ? ` It shows ${section.items} items.` : ''\n const built = screen.sections.slice(0, index).map((entry, position) => `${position + 1}. ${entry.name}`)\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n ...(type ? [`Page type: ${type.purpose}`] : []),\n ...(input.record ? [aiPageRecordLine(input.record)] : []),\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n `Build section ${index + 1} of ${screen.sections.length}: \"${section.name}\".${places}${items}`,\n built.length\n ? `Already built, above it: ${built.join('; ')}. The page’s h1 is in section 1.`\n : 'Nothing is built yet: this section holds the page’s h1.',\n ...(input.reusableComponents === false\n ? [AI_PAGE_SECTION_INLINE_LINE, ...(section.items ? [AI_PAGE_SECTION_REPEAT_LINE] : [])]\n : section.items && section.uses.length\n ? [AI_PAGE_SECTION_INSTANCE_LINE]\n : []),\n `Keep this section to at most ${input.maxElements} elements.`,\n ].join('\\n')\n}\n\n/** A validated section: its nodes keyed as the page stores them, its root under the page root. */\nexport interface AiPageSection {\n rootId: string\n nodes: NodesMap\n /** The page's weight with the section added, as the doctrine measured it. */\n load: AiLoadEstimate | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A page with nothing on it yet: the document wrapper. */\nexport function aiEmptyPage(): NodesMap {\n return {\n [CANVAS_ROOT_ELEMENT_ID]: {\n $id: CANVAS_ROOT_ELEMENT_ID,\n componentId: 'div',\n parentId: null,\n nodes: [],\n },\n } as unknown as NodesMap\n}\n\n/**\n * The page with a section in its plan position: after every section with a\n * lower index and before every one with a higher, the page's other children\n * — whatever a member placed — kept where they are. A section already on the\n * page is replaced, never doubled.\n */\nexport function aiPageWithSection(\n page: NodesMap,\n section: AiPageSection,\n sectionIds: readonly string[],\n): NodesMap {\n const nodes: Record<string, Record<string, unknown>> = {}\n const previous = (page as unknown as Record<string, Record<string, unknown>>)[section.rootId]\n const replaced = new Set<string>()\n if (previous) {\n for (const { id } of walkTree({\n rootId: section.rootId,\n nodes: page as unknown as Record<string, AiDoctrineNode>,\n })) {\n replaced.add(id)\n }\n }\n for (const [id, node] of Object.entries(page)) {\n if (!replaced.has(id)) nodes[id] = { ...(node as unknown as Record<string, unknown>) }\n }\n const root = nodes[CANVAS_ROOT_ELEMENT_ID] ?? (aiEmptyPage()[CANVAS_ROOT_ELEMENT_ID] as unknown as Record<string, unknown>)\n const children = (Array.isArray(root['nodes']) ? (root['nodes'] as string[]) : []).filter(\n (id) => id !== section.rootId,\n )\n const order = new Map(sectionIds.map((id, position) => [id, position]))\n const at = order.get(section.rootId) ?? sectionIds.length\n const before = children.findIndex((id) => (order.get(id) ?? -1) > at)\n children.splice(before === -1 ? children.length : before, 0, section.rootId)\n nodes[CANVAS_ROOT_ELEMENT_ID] = { ...root, nodes: children }\n for (const [id, node] of Object.entries(section.nodes)) {\n nodes[id] = { ...(node as unknown as Record<string, unknown>) }\n }\n return nodes as unknown as NodesMap\n}\n\n/**\n * What a tree generated against this inventory may reference, the brand it is\n * held to, where the workspace keeps no reusable components\n * (AGL-3030) that the page is built inline, the sections its plan names,\n * which a link may take a visitor to (AGL-3097), and the players its plan\n * lists for this page, the only ones it may embed (AGL-3433).\n */\nexport function aiPageCheckContext(\n inventory: AiSiteInventory | null,\n options: {\n reusableComponents?: boolean\n sections?: readonly string[]\n embeds?: AiDoctrineTreeContext['plannedEmbeds']\n /** A record template's tokens, which its links and images may name (AGL-3475). */\n recordTokens?: readonly string[]\n } = {},\n): AiDoctrineTreeContext {\n return {\n brand: inventory?.theme ? { colors: inventory.theme.colors, fonts: inventory.theme.fonts } : null,\n ...aiNodeTreeContextFromInventory(inventory),\n homeScreenIds: aiHomeScreenIds(inventory),\n ...(options.reusableComponents === false ? { reusableComponents: false } : {}),\n ...(options.sections?.length ? { pageSections: options.sections } : {}),\n ...(options.embeds?.length ? { plannedEmbeds: options.embeds } : {}),\n ...(options.recordTokens?.length ? { bindingTokens: options.recordTokens } : {}),\n }\n}\n\nexport interface AiPageSectionCheckInput {\n /** The page as stored so far; an empty page on the first pass. */\n page: NodesMap\n /** Every section root id of the plan, in order. */\n sectionIds: readonly string[]\n index: number\n context: AiDoctrineTreeContext\n /**\n * The plan line this pass answers for: what it places by inventory id, and\n * how many items it promised, which the answer is held to (AGL-3024).\n */\n section: Pick<AiBuildPlanSection, 'name' | 'uses' | 'items'>\n inventory: AiSiteInventory | null\n}\n\n/** The parts of an answer the violations name, as the model wrote them, capped as the doctrine caps a re-ask. */\nfunction offendingOf(raw: unknown, violations: readonly AiDoctrineViolation[]): { offending?: Record<string, unknown> } {\n const rawNodes = isRecord(raw) && isRecord(raw['nodes']) ? raw['nodes'] : {}\n const offending: Record<string, unknown> = {}\n let used = 2\n for (const id of new Set(violations.flatMap((violation) => violation.nodeIds ?? []))) {\n if (rawNodes[id] === undefined) continue\n const size = JSON.stringify(rawNodes[id]).length + id.length + 4\n if (used + size > AI_REASK_OFFENDING_MAX_CHARS) break\n offending[id] = rawNodes[id]\n used += size\n }\n return Object.keys(offending).length ? { offending } : {}\n}\n\n/**\n * The check a section's answer is held to: a repeated item written once drawn\n * into its copies (AGL-3053), then the palette validator on the section, with\n * any line it cut at its ceiling refused (AGL-3076), the doctrine's page check\n * on the page with the section added, and the plan line — every component it\n * names placed as an instance, every form bound by its id (rule 7), and as\n * many items as it promised (AGL-3024). Violations\n * name the section's own nodes by the ids the MODEL wrote, and a copy's nodes\n * by the item's; one that names only nodes an earlier pass stored belongs to\n * the page rather than to this answer, and is left to the last pass (AGL-3078).\n */\nexport function aiPageSectionCheck(input: AiPageSectionCheckInput): AiGenerationCheck<AiPageSection> {\n const sectionId = input.sectionIds[input.index]\n const components = new Set((input.inventory?.components ?? []).map((row) => row.id))\n const forms = new Set((input.inventory?.forms ?? []).map((row) => row.id))\n return (answer) => {\n const raw = aiAnswerTree(answer)\n // Before anything is drawn or dropped: a repeated item written and never\n // placed is content the section loses silently, and a child named and\n // never written ends the pass with nothing to answer (AGL-3143).\n const disagreeing = detectDisagreeingNodes(raw)\n if (disagreeing.length) {\n return { value: null, violations: disagreeing, ...offendingOf(raw, disagreeing) }\n }\n const drawn = expandAiRepeatedItems(raw, { inline: input.context.reusableComponents === false, noun: 'section' })\n if (drawn.ok === false) {\n return { value: null, violations: drawn.violations, ...offendingOf(raw, drawn.violations) }\n }\n // Every id below is the drawn tree's; a copy's leads back to the node the model wrote.\n const written = (id: string): string => drawn.sourceIds[id] ?? id\n const validated = validateAiNodeTree(drawn.tree, 'screen', input.context)\n if (validated.ok === false) {\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: `tree-${validated.code}`,\n message: 'The answer could not be used as a section.',\n detail: validated.error,\n },\n ],\n }\n }\n const wrapper = validated.nodes[validated.rootId] as unknown as AiDoctrineNode | undefined\n const top = (wrapper?.nodes ?? []).filter((id) => validated.nodes[id])\n const sectionNode = top.length === 1 ? (validated.nodes[top[0]] as unknown as AiDoctrineNode) : null\n if (!sectionNode || sectionNode.componentId !== 'section') {\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'section-shape',\n message: 'Answer with exactly one Section element inside the document wrapper.',\n },\n ],\n }\n }\n\n // The section's root takes its plan id, so a pass run again finds it, and\n // hangs under the page root; every other minted id stays as minted.\n const minted = top[0]\n const modelIds: Record<string, string> = Object.fromEntries(\n Object.entries(validated.sourceIds).map(([id, source]) => [id, written(source)]),\n )\n modelIds[sectionId] = written(validated.sourceIds[minted] ?? minted)\n const sectionNodes: Record<string, Record<string, unknown>> = {}\n for (const { id, node } of walkTree({\n rootId: minted,\n nodes: validated.nodes as unknown as Record<string, AiDoctrineNode>,\n })) {\n const stored = { ...(node as unknown as Record<string, unknown>) }\n if (id === minted) {\n stored['$id'] = sectionId\n stored['parentId'] = CANVAS_ROOT_ELEMENT_ID\n } else if (stored['parentId'] === minted) {\n stored['parentId'] = sectionId\n }\n sectionNodes[id === minted ? sectionId : id] = stored\n }\n const drawnNodes = isRecord(drawn.tree) && isRecord(drawn.tree['nodes']) ? drawn.tree['nodes'] : {}\n // What the model wrote of one of the section's nodes, by the id the page stores it under.\n const drawnIdOf = (stored: string): string =>\n validated.sourceIds[stored === sectionId ? minted : stored] ?? stored\n // A link that names a section of this page carries the platform's Scroll to\n // element interaction to that section's root, whose id is minted from the\n // plan before the section is built (AGL-3097).\n const sections = input.context.pageSections ?? []\n for (const [id, node] of Object.entries(sectionNodes)) {\n const props = isRecord(node['props']) ? node['props'] : {}\n if (!isAiLinkElement(node['componentId']) || props['screenId'] || props['href']) continue\n const target = aiPageLinkTarget(drawnNodes[drawnIdOf(id)], sections)\n if (target?.kind === 'section') {\n node['interactions'] = [aiPageScrollInteraction(input.sectionIds[target.section], sections[target.section])]\n }\n }\n const section: AiPageSection = { rootId: sectionId, nodes: sectionNodes as unknown as NodesMap, load: null }\n\n const page = aiPageWithSection(input.page, section, input.sectionIds)\n const own = new Set(Object.keys(sectionNodes))\n // The page check reads the section as the page stores it, where the palette\n // validator has already dropped what it could not read, such as a\n // `container` written as text or a link's `scrollTo`; what the model wrote\n // of each of the section's nodes is read from the section as it was drawn\n // (AGL-3078). A link the page already carries to one of its section roots\n // goes there, whichever pass wrote it (AGL-3097).\n const report = validateAiDoctrineTree({ rootId: CANVAS_ROOT_ELEMENT_ID, nodes: page }, 'page', {\n ...input.context,\n writtenNode: (id) => (own.has(id) ? drawnNodes[drawnIdOf(id)] : undefined),\n scrollTargetIds: input.sectionIds,\n })\n // The page check mints its ids afresh: its map leads back to the page's\n // ids, and the section's own lead back to the model's.\n const pageIds = report.tree?.sourceIds ?? {}\n const toModel = (id: string): string | null => {\n const stored = pageIds[id] ?? id\n return own.has(stored) ? (modelIds[stored] ?? stored) : null\n }\n // The page check reads the section as it is stored, where a line the\n // palette validator cut is already cut, so the cut is read from the\n // section's own validation, first (AGL-3076). Copies of one item name one\n // node the model wrote, once.\n //\n // A PASS ANSWERS ONLY FOR THE SECTION IT WROTE (AGL-3078). The page check\n // reads the whole page, so it also names nodes an earlier pass stored; the\n // model is not shown those and cannot mend them, so a finding that names\n // none of this section's nodes is not held against this answer. Dropping\n // it neither hides nor forgives it: the last pass runs the same check on\n // the finished page, where every node is the page's and the review names\n // each one and outlines it. Kept, such a finding would re-ask the model\n // for a node it never saw, refuse the same answer twice, and stop the page\n // with findings whose ids all filtered away — no node ids, and so no\n // outline either.\n const violations: AiDoctrineViolation[] = [\n ...detectCutLines(validated.repairs, drawnNodes, written),\n ...report.violations.flatMap((violation) => {\n if (!violation.nodeIds?.length) return [violation]\n const named = [...new Set(violation.nodeIds.map(toModel).filter((id): id is string => id !== null))]\n return named.length ? [{ ...violation, nodeIds: named }] : []\n }),\n ]\n\n const placed = new Set<string>()\n const bound = new Set<string>()\n for (const node of Object.values(sectionNodes)) {\n const props = isRecord(node['props']) ? node['props'] : {}\n if (node['componentId'] === REUSABLE_INSTANCE_COMPONENT_ID && typeof props[AI_INSTANCE_REF_PROP] === 'string') {\n placed.add(props[AI_INSTANCE_REF_PROP] as string)\n }\n if (node['componentId'] === 'form' && typeof props['formId'] === 'string') bound.add(props['formId'])\n }\n const missing = input.section.uses.filter(\n (ref) => (components.has(ref) && !placed.has(ref)) || (forms.has(ref) && !bound.has(ref)),\n )\n if (missing.length) {\n violations.push({\n rule: 7,\n code: 'plan-reuse-not-placed',\n message: `The confirmed plan places ${missing.join(', ')} in this section, and the section does not. Place each one by its id.`,\n })\n }\n // The count the plan promised, against the section as the page stores it\n // (AGL-3024): the prompt states it, and until now nothing read it back.\n violations.push(\n ...aiPlanItemCountViolations(input.section, {\n rootId: sectionId,\n nodes: sectionNodes as unknown as Record<string, AiDoctrineNode>,\n }),\n )\n\n return {\n value: { ...section, load: report.load },\n violations,\n ...offendingOf(raw, violations),\n }\n }\n}\n"],"names":["REUSABLE_INSTANCE_COMPONENT_ID","CANVAS_ROOT_ELEMENT_ID","aiPlanCreateFor","aiPageTypeDefinition","parseAiPageJobInputs","aiHomeScreenIds","AI_REASK_OFFENDING_MAX_CHARS","aiAnswerTree","aiNodeTreeContextFromInventory","detectCutLines","detectDisagreeingNodes","isAiLinkElement","validateAiDoctrineTree","walkTree","AI_INSTANCE_REF_PROP","validateAiNodeTree","aiPageLinkTarget","aiPageScrollInteraction","AI_PALETTE_CATALOG","AI_REPEAT_KEY","expandAiRepeatedItems","aiJobBriefLine","aiPlanReferenceLines","aiPlanItemCountViolations","aiPageSectionNodeId","jobId","index","slice","AI_PAGE_SECTION_TOOL","name","description","strict","inputSchema","type","additionalProperties","required","properties","tree","AI_JOB_PAGE_INSTRUCTIONS","text","join","screen","cacheBreakpoint","referenceOf","plan","ref","label","labels","aiPageRecordTemplate","record","inventory","created","row","datasets","find","entry","id","dataset","base","fields","map","fieldIds","aiPageRecordTokens","template","field","aiPageRecordLine","length","aiPageRecordNote","AI_PAGE_SECTION_INLINE_LINE","AI_PAGE_SECTION_REPEAT_SHAPE","AI_PAGE_SECTION_REPEAT_LINE","AI_PAGE_SECTION_INSTANCE_LINE","aiPageSectionSmaller","input","reusableComponents","maxElements","aiPageSectionPrompt","job","inputs","pageType","section","sections","places","uses","items","built","position","title","slug","purpose","isRecord","value","Array","isArray","aiEmptyPage","$id","componentId","parentId","nodes","aiPageWithSection","page","sectionIds","order","previous","rootId","replaced","Set","add","node","Object","entries","has","root","children","filter","Map","at","get","before","findIndex","splice","aiPageCheckContext","options","brand","theme","colors","fonts","homeScreenIds","pageSections","embeds","plannedEmbeds","recordTokens","bindingTokens","offendingOf","raw","violations","rawNodes","offending","used","flatMap","violation","nodeIds","undefined","size","JSON","stringify","keys","aiPageSectionCheck","sectionId","components","forms","answer","validated","report","disagreeing","drawn","inline","context","noun","ok","written","sourceIds","rule","code","message","detail","error","wrapper","top","sectionNode","minted","modelIds","fromEntries","source","sectionNodes","stored","drawnNodes","drawnIdOf","props","target","kind","load","own","writtenNode","scrollTargetIds","pageIds","toModel","repairs","named","placed","bound","values","missing","push"],"mappings":"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"}
@@ -19,6 +19,7 @@ import { type AiBuildPlanScreen } from '../model/ai-build-plan';
19
19
  import type { AiJob, AiJobPlan, AiJobReview } from '../model/ai-jobs.types';
20
20
  import { generateSeoFields } from '../runtime/seo-fields';
21
21
  import { readSiteInventory } from '../runtime/site-inventory';
22
+ import type { AiSiteInventory } from '../model/ai-site-inventory';
22
23
  import { type AiJobAdmission } from './ai-job-admission';
23
24
  import { AI_JOB_PAGE_SECTION_MAX_TOKENS, AI_JOB_PAGE_SECTION_TOKENS, AI_JOB_PAGE_STEP_BUDGET, AI_JOB_PAGE_STEP_MINIMUM_MS, aiJobPageSectionMaxTokens } from './ai-job-page-budget';
24
25
  import { type AiSiteUnit } from './ai-job-site-step';
@@ -148,8 +149,25 @@ export declare function aiPageJobUnits(plan: Pick<AiJobPlan, 'create'>): AiSiteU
148
149
  * needs a section pass's.
149
150
  */
150
151
  export declare function aiPageJobRunMinimumMs(job: AiJob): number;
151
- /** The one-segment address a draft asks for, from the plan's slug. */
152
+ /**
153
+ * The one-segment address a draft asks for, from the plan's slug; `/` for a
154
+ * planned home page, which asks for the root rather than for an address made
155
+ * from its name (AGL-3596).
156
+ */
152
157
  export declare function aiPageDraftSlug(screen: Pick<AiBuildPlanScreen, 'slug'>): string;
158
+ /**
159
+ * The layout a site's pages render inside (AGL-3596): the one its home page
160
+ * uses, else the one the most pages use, else the site's only layout. `null`
161
+ * for a site with no layout, or several and no page to choose between them.
162
+ */
163
+ export declare function aiSiteDefaultLayoutId(inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>): string | null;
164
+ /**
165
+ * The layout a page draft renders inside: the plan's, where the site has it,
166
+ * else the site's own (AGL-3596). A page the plan named no layout for used to
167
+ * be written with none, and so with no header and no footer, beside pages
168
+ * that had them.
169
+ */
170
+ export declare function aiPageDraftLayoutId(screen: Pick<AiBuildPlanScreen, 'layout'>, inventory: Pick<AiSiteInventory, 'layouts' | 'screens'>): string | null;
153
171
  /**
154
172
  * The counts the confirmed plan's sections promised, against the page a COPY
155
173
  * actually produced (AGL-3024); `null` when the copy keeps the plan, or when