@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
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
18
+ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
19
+ import { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol';
20
+ import { type Firestore } from 'firebase-admin/firestore';
21
+ import type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types';
22
+ /**
23
+ * A guided site start publishes what it built (AGL-3596).
24
+ *
25
+ * A person who asked for "a website" in the guided start and watched it build
26
+ * was then handed drafts: a site whose address still answered with the
27
+ * starter, or with nothing. A `site` job started from the guided start (its
28
+ * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends
29
+ * by putting every page it built on the site, through the same writes a
30
+ * person's Publish makes:
31
+ *
32
+ * - each page's address in the host's routing map, `publishedAt` on the page,
33
+ * in ONE batch with the publish outbox entry that drops the live site's
34
+ * cached pages (AGL-2575), the drop itself attempted at once;
35
+ * - the home page takes `/` from the starter home page the site was created
36
+ * with (AGL-3408), and an untouched starter goes to the trash rather than
37
+ * staying beside it as a second "Home";
38
+ * - the navigation entries the pages proposed are added to the header of
39
+ * the layout they render inside, as a new live version of that layout;
40
+ * - `site_published` once, server-side, as the scheduled publish sends it.
41
+ *
42
+ * A page that cannot be published — deleted meanwhile, no address, an address
43
+ * another page holds — stays a draft and is reported with the plain sentence
44
+ * for why. It never fails the job: the site is still built, and the person
45
+ * fixes that one page in Pages. Every other job kind keeps producing drafts.
46
+ *
47
+ * The allowance is respected by construction: a draft is counted against the
48
+ * plan's pages when it is written (`aiDraftBandRefusal`), so publishing
49
+ * drafts adds none, and retiring the starter frees the one it held.
50
+ */
51
+ export interface AiSitePublishDeps {
52
+ sendSitePublished?: typeof sendGa4SitePublished;
53
+ dropCache?: typeof dropPluginSiteCache;
54
+ }
55
+ /** Whether a finished job of this shape publishes what it built. */
56
+ export declare function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
57
+ /**
58
+ * Where a site answers: its custom domain, else its subdomain under the
59
+ * deployment's tenant apex — never a hard-coded platform host, so a
60
+ * self-hosted deployment links to its own sites.
61
+ */
62
+ export declare function aiSiteLiveUrl(host: {
63
+ cname?: unknown;
64
+ subdomain?: unknown;
65
+ }): string | null;
66
+ /** The sentences a page that stayed a draft is reported with. */
67
+ export declare const AI_SITE_PUBLISH_DELETED = "It was deleted before it could be published.";
68
+ export declare const AI_SITE_PUBLISH_NO_ADDRESS = "It has no address yet. Give it one in Pages and publish it.";
69
+ export declare const aiSitePublishTakenCopy: (path: string) => string;
70
+ export declare const AI_SITE_PUBLISH_FAILED = "It could not be published just now. Publish it from Pages.";
71
+ /** A navigation entry a page proposed, as the page step reports it. */
72
+ interface NavEntry {
73
+ screenId: string;
74
+ label: string;
75
+ }
76
+ /**
77
+ * The layout's nodes with its links to the retired starter home pointed at
78
+ * the new home, and a link for each proposed entry the header does not
79
+ * already carry, placed in the header's toolbar after its existing links.
80
+ * `null` when nothing changes, or when the layout has no toolbar to hold an
81
+ * entry and no link to repoint.
82
+ */
83
+ export declare function aiLayoutWithNavigation(nodes: NodesMap, input: {
84
+ entries: readonly NavEntry[];
85
+ retiredHomeId?: string | null;
86
+ homeId?: string | null;
87
+ }): NodesMap | null;
88
+ /**
89
+ * Publishes the pages a guided site start built. Never throws for a page; a
90
+ * Firestore failure of the publish write itself reports every page as a
91
+ * draft, for the same reason, and is logged.
92
+ */
93
+ export declare function aiPublishGuidedSite(firestore: Firestore, input: {
94
+ job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>;
95
+ outputs: readonly AiJobOutput[];
96
+ now: Date;
97
+ }, deps?: AiSitePublishDeps): Promise<AiJobSitePublish>;
98
+ export {};
@@ -0,0 +1,372 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { isFirstPublishedRoute } from "@aglyn/aglyn/app-utils/analytics-events";
18
+ import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
19
+ import { hostPublicOrigin } from "@aglyn/aglyn/app-utils/host-naming";
20
+ import { blockingRouteOwner, normalizeScreenSlug, SCREEN_ROOT_PATH, screenRoutePathToUrl } from "@aglyn/aglyn/app-utils/screen-route";
21
+ import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
22
+ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
23
+ import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
24
+ import { FieldValue } from "firebase-admin/firestore";
25
+ /** Whether a finished job of this shape publishes what it built. */ export function aiJobPublishesSite(job) {
26
+ var _job_inputs;
27
+ return job.kind === 'site' && ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['autoConfirm']) === true;
28
+ }
29
+ /**
30
+ * Where a site answers: its custom domain, else its subdomain under the
31
+ * deployment's tenant apex — never a hard-coded platform host, so a
32
+ * self-hosted deployment links to its own sites.
33
+ */ export function aiSiteLiveUrl(host) {
34
+ const text = (value)=>typeof value === 'string' ? value.trim() : '';
35
+ const origin = hostPublicOrigin({
36
+ cname: text(host.cname) || null,
37
+ subdomain: text(host.subdomain) || null
38
+ });
39
+ return origin ? `${origin}/` : null;
40
+ }
41
+ /** The sentences a page that stayed a draft is reported with. */ export const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.';
42
+ export const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.';
43
+ export const aiSitePublishTakenCopy = (path)=>`Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`;
44
+ export const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.';
45
+ function navEntriesOf(outputs) {
46
+ const entries = [];
47
+ for (const output of outputs){
48
+ var _output_proposal;
49
+ if (output.resource !== 'screen') continue;
50
+ const navigation = (_output_proposal = output.proposal) == null ? void 0 : _output_proposal['navigation'];
51
+ const label = typeof (navigation == null ? void 0 : navigation.label) === 'string' ? navigation.label.trim() : '';
52
+ if (label && !entries.some((entry)=>entry.screenId === output.id)) {
53
+ entries.push({
54
+ screenId: output.id,
55
+ label
56
+ });
57
+ }
58
+ }
59
+ return entries;
60
+ }
61
+ /**
62
+ * The layout's nodes with its links to the retired starter home pointed at
63
+ * the new home, and a link for each proposed entry the header does not
64
+ * already carry, placed in the header's toolbar after its existing links.
65
+ * `null` when nothing changes, or when the layout has no toolbar to hold an
66
+ * entry and no link to repoint.
67
+ */ export function aiLayoutWithNavigation(nodes, input) {
68
+ const map = {};
69
+ for (const [id, node] of Object.entries(nodes)){
70
+ map[id] = _extends({}, node, node.props ? {
71
+ props: _extends({}, node.props)
72
+ } : {}, node.nodes ? {
73
+ nodes: [
74
+ ...node.nodes
75
+ ]
76
+ } : {});
77
+ }
78
+ let changed = false;
79
+ const links = Object.values(map).filter((node)=>node.componentId === 'muiScreenLink');
80
+ if (input.retiredHomeId && input.homeId) {
81
+ for (const link of links){
82
+ var _link_props;
83
+ if (((_link_props = link.props) == null ? void 0 : _link_props['screenId']) === input.retiredHomeId) {
84
+ link.props = _extends({}, link.props, {
85
+ screenId: input.homeId
86
+ });
87
+ changed = true;
88
+ }
89
+ }
90
+ }
91
+ const linked = new Set(links.map((link)=>{
92
+ var _link_props;
93
+ return (_link_props = link.props) == null ? void 0 : _link_props['screenId'];
94
+ }).filter(Boolean));
95
+ const toolbar = Object.values(map).find((node)=>node.componentId === 'muiToolbar');
96
+ const missing = input.entries.filter((entry)=>!linked.has(entry.screenId));
97
+ if (toolbar && missing.length) {
98
+ var _toolbar_nodes;
99
+ const children = (_toolbar_nodes = toolbar.nodes) != null ? _toolbar_nodes : [];
100
+ // After the last link already in the toolbar (the brand), before the
101
+ // search, the mode switch and the call to action.
102
+ const lastLink = children.reduce((at, id, index)=>{
103
+ var _map_id;
104
+ return ((_map_id = map[id]) == null ? void 0 : _map_id.componentId) === 'muiScreenLink' ? index : at;
105
+ }, -1);
106
+ const added = missing.map((entry)=>{
107
+ const id = `ai_nav_${createResourceUid()}`;
108
+ map[id] = {
109
+ $id: id,
110
+ componentId: 'muiScreenLink',
111
+ pluginId: 'mui',
112
+ parentId: toolbar.$id,
113
+ props: {
114
+ children: entry.label,
115
+ screenId: entry.screenId,
116
+ renderAs: 'link',
117
+ color: 'inherit'
118
+ },
119
+ sx: {
120
+ textDecoration: 'none',
121
+ whiteSpace: 'nowrap'
122
+ },
123
+ nodes: []
124
+ };
125
+ return id;
126
+ });
127
+ toolbar.nodes = [
128
+ ...children.slice(0, lastLink + 1),
129
+ ...added,
130
+ ...children.slice(lastLink + 1)
131
+ ];
132
+ changed = true;
133
+ }
134
+ return changed ? map : null;
135
+ }
136
+ /**
137
+ * Publishes the pages a guided site start built. Never throws for a page; a
138
+ * Firestore failure of the publish write itself reports every page as a
139
+ * draft, for the same reason, and is logged.
140
+ */ export async function aiPublishGuidedSite(firestore, input, deps = {}) {
141
+ var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _ref;
142
+ const sendSitePublished = (_deps_sendSitePublished = deps.sendSitePublished) != null ? _deps_sendSitePublished : sendGa4SitePublished;
143
+ const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
144
+ const { job, now } = input;
145
+ const hostId = (_job_hostId = job.hostId) != null ? _job_hostId : '';
146
+ const pages = input.outputs.filter((output, index, all)=>{
147
+ var _output_hostId;
148
+ return output.resource === 'screen' && ((_output_hostId = output.hostId) != null ? _output_hostId : hostId) === hostId && all.findIndex((other)=>other.resource === 'screen' && other.id === output.id) === index;
149
+ });
150
+ const hostRef = firestore.collection('hosts').doc(hostId);
151
+ const host = await hostRef.get();
152
+ const result = {
153
+ liveUrl: aiSiteLiveUrl({
154
+ cname: host.get('cname'),
155
+ subdomain: host.get('subdomain')
156
+ }),
157
+ published: [],
158
+ drafts: []
159
+ };
160
+ if (!host.exists || !pages.length) return result;
161
+ const before = _extends({}, (_host_get = host.get('screens')) != null ? _host_get : {});
162
+ const placeholder = host.get('defaultHomeScreenId') || null;
163
+ const routing = _extends({}, before);
164
+ const entries = {};
165
+ const accepted = [];
166
+ for (const page of pages){
167
+ var _ref1, _snapshot_get, _snapshot_get1;
168
+ const snapshot = await hostRef.collection('screens').doc(page.id).get();
169
+ const label = String((_ref1 = (_snapshot_get = snapshot.get('displayName')) != null ? _snapshot_get : page.label) != null ? _ref1 : '') || page.label;
170
+ if (!snapshot.exists || snapshot.get('deletedAt') != null) {
171
+ result.drafts.push({
172
+ id: page.id,
173
+ label,
174
+ reason: AI_SITE_PUBLISH_DELETED
175
+ });
176
+ continue;
177
+ }
178
+ const slug = normalizeScreenSlug(String((_snapshot_get1 = snapshot.get('slug')) != null ? _snapshot_get1 : ''));
179
+ if (!slug) {
180
+ result.drafts.push({
181
+ id: page.id,
182
+ label,
183
+ reason: AI_SITE_PUBLISH_NO_ADDRESS
184
+ });
185
+ continue;
186
+ }
187
+ const owner = blockingRouteOwner(routing, slug, placeholder);
188
+ if (owner && owner !== page.id) {
189
+ result.drafts.push({
190
+ id: page.id,
191
+ label,
192
+ reason: aiSitePublishTakenCopy(slug)
193
+ });
194
+ continue;
195
+ }
196
+ routing[page.id] = slug;
197
+ entries[page.id] = slug;
198
+ accepted.push({
199
+ id: page.id,
200
+ label,
201
+ path: slug,
202
+ slug
203
+ });
204
+ }
205
+ if (!accepted.length) return result;
206
+ // The first real home page replaces the placeholder in the same write.
207
+ const home = (_accepted_find = accepted.find((page)=>page.path === SCREEN_ROOT_PATH)) != null ? _accepted_find : null;
208
+ const releases = home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH ? placeholder : null;
209
+ // An untouched starter home (still its one first version) is retired to the
210
+ // trash; one its owner edited stays in Pages as their draft.
211
+ const retire = releases ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1 : false;
212
+ // The header of the layout the pages render inside learns their entries.
213
+ const layoutWrite = await aiNavigationLayoutWrite(firestore, {
214
+ hostId,
215
+ pageIds: accepted.map((page)=>page.id),
216
+ entries: navEntriesOf(pages).filter((entry)=>entries[entry.screenId]),
217
+ retiredHomeId: releases,
218
+ homeId: (_ref = home == null ? void 0 : home.id) != null ? _ref : null,
219
+ jobId: job.$id,
220
+ now
221
+ }).catch((error)=>{
222
+ console.error('ai site publish: navigation not applied', {
223
+ hostId,
224
+ jobId: job.$id,
225
+ error
226
+ });
227
+ return null;
228
+ });
229
+ const paths = [
230
+ ...new Set([
231
+ ...Object.values(entries),
232
+ ...releases ? [
233
+ SCREEN_ROOT_PATH
234
+ ] : []
235
+ ].map((path)=>screenRoutePathToUrl(path)))
236
+ ];
237
+ const batch = firestore.batch();
238
+ const hostUpdates = {
239
+ updatedAt: now
240
+ };
241
+ for (const [id, path] of Object.entries(entries))hostUpdates[`screens.${id}`] = path;
242
+ if (releases) {
243
+ hostUpdates[`screens.${releases}`] = FieldValue.delete();
244
+ hostUpdates['defaultHomeScreenId'] = FieldValue.delete();
245
+ batch.set(hostRef.collection('screens').doc(releases), _extends({
246
+ publishedAt: FieldValue.delete()
247
+ }, retire ? {
248
+ deletedAt: now
249
+ } : {}), {
250
+ merge: true
251
+ });
252
+ } else if (placeholder && entries[placeholder]) {
253
+ hostUpdates['defaultHomeScreenId'] = FieldValue.delete();
254
+ }
255
+ batch.update(hostRef, hostUpdates);
256
+ for (const page of accepted){
257
+ batch.set(hostRef.collection('screens').doc(page.id), {
258
+ slug: page.slug,
259
+ publishedAt: now
260
+ }, {
261
+ merge: true
262
+ });
263
+ }
264
+ if (layoutWrite) {
265
+ batch.create(layoutWrite.versionRef, layoutWrite.version);
266
+ batch.set(layoutWrite.layoutRef, {
267
+ versionId: layoutWrite.versionRef.id,
268
+ updatedAt: now
269
+ }, {
270
+ merge: true
271
+ });
272
+ }
273
+ // The announce, written down in the publish's own batch (AGL-2575): a
274
+ // layout change reaches every page, so it asks for the whole site.
275
+ const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid());
276
+ batch.set(outboxRef, _extends({
277
+ hostId,
278
+ paths: paths.length ? paths : [
279
+ SCREEN_ROOT_PATH
280
+ ],
281
+ createdAt: FieldValue.serverTimestamp(),
282
+ attempts: 0
283
+ }, layoutWrite ? {
284
+ entireHost: true
285
+ } : {}));
286
+ try {
287
+ await batch.commit();
288
+ } catch (error) {
289
+ console.error('ai site publish failed', {
290
+ hostId,
291
+ jobId: job.$id,
292
+ error
293
+ });
294
+ for (const page of accepted)result.drafts.push({
295
+ id: page.id,
296
+ label: page.label,
297
+ reason: AI_SITE_PUBLISH_FAILED
298
+ });
299
+ return result;
300
+ }
301
+ result.published = accepted.map(({ id, label, path })=>({
302
+ id,
303
+ label,
304
+ path: screenRoutePathToUrl(path)
305
+ }));
306
+ // The live site's cached pages go now; the outbox entry stays for the
307
+ // drain unless the drop certainly happened.
308
+ const dropped = await dropCache(_extends({
309
+ hostIds: [
310
+ hostId
311
+ ],
312
+ reason: 'guided AI site start published'
313
+ }, layoutWrite ? {} : {
314
+ paths: {
315
+ [hostId]: paths
316
+ }
317
+ }));
318
+ if (dropped.complete) await outboxRef.delete().catch(()=>undefined);
319
+ // A site that came alive with nobody's browser on the publish (AGL-1589).
320
+ await sendSitePublished({
321
+ hostId,
322
+ firstPublish: isFirstPublishedRoute(before, placeholder)
323
+ });
324
+ return result;
325
+ }
326
+ /**
327
+ * The new live version of the layout the published pages render inside, with
328
+ * their navigation entries and the home link moved off a retired starter;
329
+ * `null` when there is nothing to change or no one layout to change.
330
+ */ async function aiNavigationLayoutWrite(firestore, input) {
331
+ if (!input.entries.length && !input.retiredHomeId) return null;
332
+ const hostRef = firestore.collection('hosts').doc(input.hostId);
333
+ const layoutIds = new Set();
334
+ for (const id of input.pageIds){
335
+ var _ref;
336
+ const screen = await hostRef.collection('screens').doc(id).get();
337
+ const versionId = screen.get('versionId');
338
+ const version = typeof versionId === 'string' && versionId ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get() : null;
339
+ const layoutId = (_ref = version == null ? void 0 : version.get('layoutId')) != null ? _ref : screen.get('layoutId');
340
+ if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId);
341
+ }
342
+ if (layoutIds.size !== 1) return null;
343
+ const [layoutId] = [
344
+ ...layoutIds
345
+ ];
346
+ const layoutRef = hostRef.collection('layouts').doc(layoutId);
347
+ const layout = await layoutRef.get();
348
+ const currentVersionId = layout.get('versionId');
349
+ if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null;
350
+ const current = await layoutRef.collection('versions').doc(currentVersionId).get();
351
+ const nodes = current.exists ? decodeStoredNodes(current.get('nodes')) : null;
352
+ if (!nodes) return null;
353
+ const next = aiLayoutWithNavigation(nodes, input);
354
+ const packed = next ? encodeStoredNodes(next) : null;
355
+ if (!next || !packed) return null;
356
+ const versionRef = layoutRef.collection('versions').doc(createResourceUid());
357
+ return {
358
+ layoutRef,
359
+ versionRef,
360
+ version: {
361
+ layoutId,
362
+ hostId: input.hostId,
363
+ displayName: 'Navigation for the new pages',
364
+ nodes: Buffer.from(packed),
365
+ createdAt: input.now,
366
+ updatedAt: input.now,
367
+ aiJobId: input.jobId
368
+ }
369
+ };
370
+ }
371
+
372
+ //# sourceMappingURL=ai-site-publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-publish.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 { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\nimport {\n blockingRouteOwner,\n normalizeScreenSlug,\n SCREEN_ROOT_PATH,\n screenRoutePathToUrl,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\n\n/**\n * A guided site start publishes what it built (AGL-3596).\n *\n * A person who asked for \"a website\" in the guided start and watched it build\n * was then handed drafts: a site whose address still answered with the\n * starter, or with nothing. A `site` job started from the guided start (its\n * plan confirmed on the person's behalf, `inputs.autoConfirm`) therefore ends\n * by putting every page it built on the site, through the same writes a\n * person's Publish makes:\n *\n * - each page's address in the host's routing map, `publishedAt` on the page,\n * in ONE batch with the publish outbox entry that drops the live site's\n * cached pages (AGL-2575), the drop itself attempted at once;\n * - the home page takes `/` from the starter home page the site was created\n * with (AGL-3408), and an untouched starter goes to the trash rather than\n * staying beside it as a second \"Home\";\n * - the navigation entries the pages proposed are added to the header of\n * the layout they render inside, as a new live version of that layout;\n * - `site_published` once, server-side, as the scheduled publish sends it.\n *\n * A page that cannot be published — deleted meanwhile, no address, an address\n * another page holds — stays a draft and is reported with the plain sentence\n * for why. It never fails the job: the site is still built, and the person\n * fixes that one page in Pages. Every other job kind keeps producing drafts.\n *\n * The allowance is respected by construction: a draft is counted against the\n * plan's pages when it is written (`aiDraftBandRefusal`), so publishing\n * drafts adds none, and retiring the starter frees the one it held.\n */\n\nexport interface AiSitePublishDeps {\n sendSitePublished?: typeof sendGa4SitePublished\n dropCache?: typeof dropPluginSiteCache\n}\n\n/** Whether a finished job of this shape publishes what it built. */\nexport function aiJobPublishesSite(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && job.inputs?.['autoConfirm'] === true\n}\n\n/**\n * Where a site answers: its custom domain, else its subdomain under the\n * deployment's tenant apex — never a hard-coded platform host, so a\n * self-hosted deployment links to its own sites.\n */\nexport function aiSiteLiveUrl(host: { cname?: unknown; subdomain?: unknown }): string | null {\n const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '')\n const origin = hostPublicOrigin({ cname: text(host.cname) || null, subdomain: text(host.subdomain) || null })\n return origin ? `${origin}/` : null\n}\n\n/** The sentences a page that stayed a draft is reported with. */\nexport const AI_SITE_PUBLISH_DELETED = 'It was deleted before it could be published.'\nexport const AI_SITE_PUBLISH_NO_ADDRESS = 'It has no address yet. Give it one in Pages and publish it.'\nexport const aiSitePublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in Pages and publish it.`\nexport const AI_SITE_PUBLISH_FAILED = 'It could not be published just now. Publish it from Pages.'\n\n/** A navigation entry a page proposed, as the page step reports it. */\ninterface NavEntry {\n screenId: string\n label: string\n}\n\nfunction navEntriesOf(outputs: readonly AiJobOutput[]): NavEntry[] {\n const entries: NavEntry[] = []\n for (const output of outputs) {\n if (output.resource !== 'screen') continue\n const navigation = output.proposal?.['navigation'] as { label?: unknown } | undefined\n const label = typeof navigation?.label === 'string' ? navigation.label.trim() : ''\n if (label && !entries.some((entry) => entry.screenId === output.id)) {\n entries.push({ screenId: output.id, label })\n }\n }\n return entries\n}\n\ntype MutableNode = {\n $id: string\n componentId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n [key: string]: unknown\n}\n\n/**\n * The layout's nodes with its links to the retired starter home pointed at\n * the new home, and a link for each proposed entry the header does not\n * already carry, placed in the header's toolbar after its existing links.\n * `null` when nothing changes, or when the layout has no toolbar to hold an\n * entry and no link to repoint.\n */\nexport function aiLayoutWithNavigation(\n nodes: NodesMap,\n input: { entries: readonly NavEntry[]; retiredHomeId?: string | null; homeId?: string | null },\n): NodesMap | null {\n const map: Record<string, MutableNode> = {}\n for (const [id, node] of Object.entries(nodes as unknown as Record<string, MutableNode>)) {\n map[id] = { ...node, ...(node.props ? { props: { ...node.props } } : {}), ...(node.nodes ? { nodes: [...node.nodes] } : {}) }\n }\n let changed = false\n const links = Object.values(map).filter((node) => node.componentId === 'muiScreenLink')\n if (input.retiredHomeId && input.homeId) {\n for (const link of links) {\n if (link.props?.['screenId'] === input.retiredHomeId) {\n link.props = { ...link.props, screenId: input.homeId }\n changed = true\n }\n }\n }\n const linked = new Set(links.map((link) => link.props?.['screenId']).filter(Boolean))\n const toolbar = Object.values(map).find((node) => node.componentId === 'muiToolbar')\n const missing = input.entries.filter((entry) => !linked.has(entry.screenId))\n if (toolbar && missing.length) {\n const children = toolbar.nodes ?? []\n // After the last link already in the toolbar (the brand), before the\n // search, the mode switch and the call to action.\n const lastLink = children.reduce(\n (at, id, index) => (map[id]?.componentId === 'muiScreenLink' ? index : at),\n -1,\n )\n const added = missing.map((entry) => {\n const id = `ai_nav_${createResourceUid()}`\n map[id] = {\n $id: id,\n componentId: 'muiScreenLink',\n pluginId: 'mui',\n parentId: toolbar.$id,\n props: { children: entry.label, screenId: entry.screenId, renderAs: 'link', color: 'inherit' },\n sx: { textDecoration: 'none', whiteSpace: 'nowrap' },\n nodes: [],\n }\n return id\n })\n toolbar.nodes = [...children.slice(0, lastLink + 1), ...added, ...children.slice(lastLink + 1)]\n changed = true\n }\n return changed ? (map as unknown as NodesMap) : null\n}\n\n/**\n * Publishes the pages a guided site start built. Never throws for a page; a\n * Firestore failure of the publish write itself reports every page as a\n * draft, for the same reason, and is logged.\n */\nexport async function aiPublishGuidedSite(\n firestore: Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId'>; outputs: readonly AiJobOutput[]; now: Date },\n deps: AiSitePublishDeps = {},\n): Promise<AiJobSitePublish> {\n const sendSitePublished = deps.sendSitePublished ?? sendGa4SitePublished\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const { job, now } = input\n const hostId = job.hostId ?? ''\n const pages = input.outputs.filter(\n (output, index, all) =>\n output.resource === 'screen' &&\n (output.hostId ?? hostId) === hostId &&\n all.findIndex((other) => other.resource === 'screen' && other.id === output.id) === index,\n )\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n const result: AiJobSitePublish = {\n liveUrl: aiSiteLiveUrl({ cname: host.get('cname'), subdomain: host.get('subdomain') }),\n published: [],\n drafts: [],\n }\n if (!host.exists || !pages.length) return result\n\n const before = { ...((host.get('screens') ?? {}) as Record<string, string>) }\n const placeholder = (host.get('defaultHomeScreenId') as string | undefined) || null\n const routing = { ...before }\n const entries: Record<string, string> = {}\n const accepted: Array<{ id: string; label: string; path: string; slug: string }> = []\n for (const page of pages) {\n const snapshot = await hostRef.collection('screens').doc(page.id).get()\n const label = String(snapshot.get('displayName') ?? page.label ?? '') || page.label\n if (!snapshot.exists || snapshot.get('deletedAt') != null) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_DELETED })\n continue\n }\n const slug = normalizeScreenSlug(String(snapshot.get('slug') ?? ''))\n if (!slug) {\n result.drafts.push({ id: page.id, label, reason: AI_SITE_PUBLISH_NO_ADDRESS })\n continue\n }\n const owner = blockingRouteOwner(routing, slug, placeholder)\n if (owner && owner !== page.id) {\n result.drafts.push({ id: page.id, label, reason: aiSitePublishTakenCopy(slug) })\n continue\n }\n routing[page.id] = slug\n entries[page.id] = slug\n accepted.push({ id: page.id, label, path: slug, slug })\n }\n if (!accepted.length) return result\n\n // The first real home page replaces the placeholder in the same write.\n const home = accepted.find((page) => page.path === SCREEN_ROOT_PATH) ?? null\n const releases =\n home && placeholder && placeholder !== home.id && before[placeholder] === SCREEN_ROOT_PATH\n ? placeholder\n : null\n // An untouched starter home (still its one first version) is retired to the\n // trash; one its owner edited stays in Pages as their draft.\n const retire = releases\n ? (await hostRef.collection('screens').doc(releases).collection('versions').limit(2).get()).size <= 1\n : false\n\n // The header of the layout the pages render inside learns their entries.\n const layoutWrite = await aiNavigationLayoutWrite(firestore, {\n hostId,\n pageIds: accepted.map((page) => page.id),\n entries: navEntriesOf(pages).filter((entry) => entries[entry.screenId]),\n retiredHomeId: releases,\n homeId: home?.id ?? null,\n jobId: job.$id,\n now,\n }).catch((error: unknown) => {\n console.error('ai site publish: navigation not applied', { hostId, jobId: job.$id, error })\n return null\n })\n\n const paths = [\n ...new Set(\n [\n ...Object.values(entries),\n ...(releases ? [SCREEN_ROOT_PATH] : []),\n ].map((path) => screenRoutePathToUrl(path)),\n ),\n ]\n const batch = firestore.batch()\n const hostUpdates: Record<string, unknown> = { updatedAt: now }\n for (const [id, path] of Object.entries(entries)) hostUpdates[`screens.${id}`] = path\n if (releases) {\n hostUpdates[`screens.${releases}`] = FieldValue.delete()\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n batch.set(\n hostRef.collection('screens').doc(releases),\n { publishedAt: FieldValue.delete(), ...(retire ? { deletedAt: now } : {}) },\n { merge: true },\n )\n } else if (placeholder && entries[placeholder]) {\n hostUpdates['defaultHomeScreenId'] = FieldValue.delete()\n }\n batch.update(hostRef, hostUpdates)\n for (const page of accepted) {\n batch.set(hostRef.collection('screens').doc(page.id), { slug: page.slug, publishedAt: now }, { merge: true })\n }\n if (layoutWrite) {\n batch.create(layoutWrite.versionRef, layoutWrite.version)\n batch.set(layoutWrite.layoutRef, { versionId: layoutWrite.versionRef.id, updatedAt: now }, { merge: true })\n }\n // The announce, written down in the publish's own batch (AGL-2575): a\n // layout change reaches every page, so it asks for the whole site.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n batch.set(outboxRef, {\n hostId,\n paths: paths.length ? paths : [SCREEN_ROOT_PATH],\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n ...(layoutWrite ? { entireHost: true } : {}),\n })\n try {\n await batch.commit()\n } catch (error) {\n console.error('ai site publish failed', { hostId, jobId: job.$id, error })\n for (const page of accepted) result.drafts.push({ id: page.id, label: page.label, reason: AI_SITE_PUBLISH_FAILED })\n return result\n }\n result.published = accepted.map(({ id, label, path }) => ({ id, label, path: screenRoutePathToUrl(path) }))\n\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({\n hostIds: [hostId],\n reason: 'guided AI site start published',\n ...(layoutWrite ? {} : { paths: { [hostId]: paths } }),\n })\n if (dropped.complete) await outboxRef.delete().catch(() => undefined)\n // A site that came alive with nobody's browser on the publish (AGL-1589).\n await sendSitePublished({ hostId, firstPublish: isFirstPublishedRoute(before, placeholder) })\n return result\n}\n\ninterface LayoutWrite {\n layoutRef: FirebaseFirestore.DocumentReference\n versionRef: FirebaseFirestore.DocumentReference\n version: Record<string, unknown>\n}\n\n/**\n * The new live version of the layout the published pages render inside, with\n * their navigation entries and the home link moved off a retired starter;\n * `null` when there is nothing to change or no one layout to change.\n */\nasync function aiNavigationLayoutWrite(\n firestore: Firestore,\n input: {\n hostId: string\n pageIds: readonly string[]\n entries: readonly NavEntry[]\n retiredHomeId: string | null\n homeId: string | null\n jobId: string\n now: Date\n },\n): Promise<LayoutWrite | null> {\n if (!input.entries.length && !input.retiredHomeId) return null\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const layoutIds = new Set<string>()\n for (const id of input.pageIds) {\n const screen = await hostRef.collection('screens').doc(id).get()\n const versionId = screen.get('versionId')\n const version =\n typeof versionId === 'string' && versionId\n ? await hostRef.collection('screens').doc(id).collection('versions').doc(versionId).get()\n : null\n const layoutId = version?.get('layoutId') ?? screen.get('layoutId')\n if (typeof layoutId === 'string' && layoutId) layoutIds.add(layoutId)\n }\n if (layoutIds.size !== 1) return null\n const [layoutId] = [...layoutIds]\n const layoutRef = hostRef.collection('layouts').doc(layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const nodes = current.exists ? decodeStoredNodes<NodesMap>(current.get('nodes')) : null\n if (!nodes) return null\n const next = aiLayoutWithNavigation(nodes, input)\n const packed = next ? encodeStoredNodes(next) : null\n if (!next || !packed) return null\n const versionRef = layoutRef.collection('versions').doc(createResourceUid())\n return {\n layoutRef,\n versionRef,\n version: {\n layoutId,\n hostId: input.hostId,\n displayName: 'Navigation for the new pages',\n nodes: Buffer.from(packed),\n createdAt: input.now,\n updatedAt: input.now,\n aiJobId: input.jobId,\n },\n }\n}\n"],"names":["isFirstPublishedRoute","createResourceUid","hostPublicOrigin","blockingRouteOwner","normalizeScreenSlug","SCREEN_ROOT_PATH","screenRoutePathToUrl","decodeStoredNodes","encodeStoredNodes","dropPluginSiteCache","sendGa4SitePublished","FieldValue","aiJobPublishesSite","job","kind","inputs","aiSiteLiveUrl","host","text","value","trim","origin","cname","subdomain","AI_SITE_PUBLISH_DELETED","AI_SITE_PUBLISH_NO_ADDRESS","aiSitePublishTakenCopy","path","AI_SITE_PUBLISH_FAILED","navEntriesOf","outputs","entries","output","resource","navigation","proposal","label","some","entry","screenId","id","push","aiLayoutWithNavigation","nodes","input","map","node","Object","props","changed","links","values","filter","componentId","retiredHomeId","homeId","link","linked","Set","Boolean","toolbar","find","missing","has","length","children","lastLink","reduce","at","index","added","$id","pluginId","parentId","renderAs","color","sx","textDecoration","whiteSpace","slice","aiPublishGuidedSite","firestore","deps","accepted","sendSitePublished","dropCache","now","hostId","pages","all","findIndex","other","hostRef","collection","doc","get","result","liveUrl","published","drafts","exists","before","placeholder","routing","page","snapshot","String","reason","slug","owner","home","releases","retire","limit","size","layoutWrite","aiNavigationLayoutWrite","pageIds","jobId","catch","error","console","paths","batch","hostUpdates","updatedAt","delete","set","publishedAt","deletedAt","merge","update","create","versionRef","version","layoutRef","versionId","outboxRef","createdAt","serverTimestamp","attempts","entireHost","commit","dropped","hostIds","complete","undefined","firstPublish","layoutIds","screen","layoutId","add","layout","currentVersionId","current","next","packed","displayName","Buffer","from","aiJobId"],"mappings":"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"}
@@ -98,12 +98,13 @@ export interface AiFreePageFixture extends AiPageBriefFixture {
98
98
  }
99
99
  /**
100
100
  * A page brief for a Free workspace (AGL-3030): a site that keeps no reusable
101
- * components and no saved forms, with the one layout its plan includes. Its
102
- * practice areas repeat and its consultation request is a form, so the page
103
- * is built the one way such a workspace can build it — the cards drawn where
104
- * they repeat, written once in the answer (AGL-3053), and the form carried by
105
- * the page with its fields inside it. The plan the page job keeps for it is
106
- * held to the Free workspace's capabilities, never the whole doctrine.
101
+ * components, with the one layout its plan includes and the one saved form
102
+ * its consultation requests arrive through. Its practice areas repeat, so the
103
+ * cards are drawn where they repeat, written once in the answer (AGL-3053);
104
+ * its consultation request places that saved form by its id, as every page
105
+ * does (AGL-3596) — a form is never drawn on a page. The plan the page job
106
+ * keeps for it is held to the Free workspace's capabilities, never the whole
107
+ * doctrine.
107
108
  */
108
109
  export declare const AI_FREE_PAGE_FIXTURE: AiFreePageFixture;
109
110
  /**