@aglyn/tenant-runtime 1.0.0-beta.143

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 (120) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +26 -0
  3. package/package.json +39 -0
  4. package/src/index.d.ts +21 -0
  5. package/src/index.js +22 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/apply-publish-schedule.d.ts +34 -0
  8. package/src/lib/apply-publish-schedule.js +285 -0
  9. package/src/lib/apply-publish-schedule.js.map +1 -0
  10. package/src/lib/assign-contact-owner.d.ts +162 -0
  11. package/src/lib/assign-contact-owner.js +322 -0
  12. package/src/lib/assign-contact-owner.js.map +1 -0
  13. package/src/lib/associate-company-by-domain.d.ts +70 -0
  14. package/src/lib/associate-company-by-domain.js +150 -0
  15. package/src/lib/associate-company-by-domain.js.map +1 -0
  16. package/src/lib/author-page-nodes.d.ts +36 -0
  17. package/src/lib/author-page-nodes.js +296 -0
  18. package/src/lib/author-page-nodes.js.map +1 -0
  19. package/src/lib/built-in-page-layout.d.ts +48 -0
  20. package/src/lib/built-in-page-layout.js +107 -0
  21. package/src/lib/built-in-page-layout.js.map +1 -0
  22. package/src/lib/capture-host-contact.d.ts +95 -0
  23. package/src/lib/capture-host-contact.js +137 -0
  24. package/src/lib/capture-host-contact.js.map +1 -0
  25. package/src/lib/collection-fallback-nodes.d.ts +90 -0
  26. package/src/lib/collection-fallback-nodes.js +602 -0
  27. package/src/lib/collection-fallback-nodes.js.map +1 -0
  28. package/src/lib/compose-author-page.d.ts +109 -0
  29. package/src/lib/compose-author-page.js +225 -0
  30. package/src/lib/compose-author-page.js.map +1 -0
  31. package/src/lib/compose-collection-page.d.ts +94 -0
  32. package/src/lib/compose-collection-page.js +256 -0
  33. package/src/lib/compose-collection-page.js.map +1 -0
  34. package/src/lib/compose-screen-nodes.d.ts +156 -0
  35. package/src/lib/compose-screen-nodes.js +547 -0
  36. package/src/lib/compose-screen-nodes.js.map +1 -0
  37. package/src/lib/compose-search-page.d.ts +39 -0
  38. package/src/lib/compose-search-page.js +57 -0
  39. package/src/lib/compose-search-page.js.map +1 -0
  40. package/src/lib/convert-host-lead.d.ts +189 -0
  41. package/src/lib/convert-host-lead.js +443 -0
  42. package/src/lib/convert-host-lead.js.map +1 -0
  43. package/src/lib/defer-lazy-panels.d.ts +58 -0
  44. package/src/lib/defer-lazy-panels.js +171 -0
  45. package/src/lib/defer-lazy-panels.js.map +1 -0
  46. package/src/lib/element-animation-assets.d.ts +62 -0
  47. package/src/lib/element-animation-assets.js +290 -0
  48. package/src/lib/element-animation-assets.js.map +1 -0
  49. package/src/lib/emit-host-event.d.ts +29 -0
  50. package/src/lib/emit-host-event.js +30 -0
  51. package/src/lib/emit-host-event.js.map +1 -0
  52. package/src/lib/enrich-gated-page.d.ts +67 -0
  53. package/src/lib/enrich-gated-page.js +134 -0
  54. package/src/lib/enrich-gated-page.js.map +1 -0
  55. package/src/lib/entry-link-routes.d.ts +61 -0
  56. package/src/lib/entry-link-routes.js +169 -0
  57. package/src/lib/entry-link-routes.js.map +1 -0
  58. package/src/lib/get-author-content.d.ts +119 -0
  59. package/src/lib/get-author-content.js +288 -0
  60. package/src/lib/get-author-content.js.map +1 -0
  61. package/src/lib/get-collection-content.d.ts +290 -0
  62. package/src/lib/get-collection-content.js +565 -0
  63. package/src/lib/get-collection-content.js.map +1 -0
  64. package/src/lib/get-components.d.ts +24 -0
  65. package/src/lib/get-components.js +102 -0
  66. package/src/lib/get-components.js.map +1 -0
  67. package/src/lib/get-datasets.d.ts +45 -0
  68. package/src/lib/get-datasets.js +169 -0
  69. package/src/lib/get-datasets.js.map +1 -0
  70. package/src/lib/get-forms.d.ts +24 -0
  71. package/src/lib/get-forms.js +116 -0
  72. package/src/lib/get-forms.js.map +1 -0
  73. package/src/lib/get-layout-version.d.ts +26 -0
  74. package/src/lib/get-layout-version.js +99 -0
  75. package/src/lib/get-layout-version.js.map +1 -0
  76. package/src/lib/get-media-asset-facts.d.ts +74 -0
  77. package/src/lib/get-media-asset-facts.js +116 -0
  78. package/src/lib/get-media-asset-facts.js.map +1 -0
  79. package/src/lib/get-plugin-installs.d.ts +29 -0
  80. package/src/lib/get-plugin-installs.js +94 -0
  81. package/src/lib/get-plugin-installs.js.map +1 -0
  82. package/src/lib/get-screen-version.d.ts +27 -0
  83. package/src/lib/get-screen-version.js +66 -0
  84. package/src/lib/get-screen-version.js.map +1 -0
  85. package/src/lib/get-screen.d.ts +39 -0
  86. package/src/lib/get-screen.js +126 -0
  87. package/src/lib/get-screen.js.map +1 -0
  88. package/src/lib/get-variables.d.ts +41 -0
  89. package/src/lib/get-variables.js +128 -0
  90. package/src/lib/get-variables.js.map +1 -0
  91. package/src/lib/get-video-asset-facts.d.ts +22 -0
  92. package/src/lib/get-video-asset-facts.js +22 -0
  93. package/src/lib/get-video-asset-facts.js.map +1 -0
  94. package/src/lib/host-event-listeners.d.ts +83 -0
  95. package/src/lib/host-event-listeners.js +91 -0
  96. package/src/lib/host-event-listeners.js.map +1 -0
  97. package/src/lib/org-permissions.d.ts +63 -0
  98. package/src/lib/org-permissions.js +187 -0
  99. package/src/lib/org-permissions.js.map +1 -0
  100. package/src/lib/repeat-record-pages.d.ts +48 -0
  101. package/src/lib/repeat-record-pages.js +64 -0
  102. package/src/lib/repeat-record-pages.js.map +1 -0
  103. package/src/lib/required-site-plugins.d.ts +114 -0
  104. package/src/lib/required-site-plugins.js +121 -0
  105. package/src/lib/required-site-plugins.js.map +1 -0
  106. package/src/lib/resolve-dataset.d.ts +40 -0
  107. package/src/lib/resolve-dataset.js +64 -0
  108. package/src/lib/resolve-dataset.js.map +1 -0
  109. package/src/lib/search-results-nodes.d.ts +48 -0
  110. package/src/lib/search-results-nodes.js +302 -0
  111. package/src/lib/search-results-nodes.js.map +1 -0
  112. package/src/lib/social-image-facts.d.ts +107 -0
  113. package/src/lib/social-image-facts.js +133 -0
  114. package/src/lib/social-image-facts.js.map +1 -0
  115. package/src/lib/stamp-form-dataset-bindings.d.ts +32 -0
  116. package/src/lib/stamp-form-dataset-bindings.js +59 -0
  117. package/src/lib/stamp-form-dataset-bindings.js.map +1 -0
  118. package/src/lib/template-screens.d.ts +227 -0
  119. package/src/lib/template-screens.js +364 -0
  120. package/src/lib/template-screens.js.map +1 -0
@@ -0,0 +1,256 @@
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 * as Aglyn from "@aglyn/aglyn/server";
18
+ import buildCollectionFallbackNodes from "./collection-fallback-nodes.js";
19
+ import composeScreenNodes, { composeNodesWithChrome } from "./compose-screen-nodes.js";
20
+ import { resolveBuiltInPageLayoutId } from "./built-in-page-layout.js";
21
+ import getScreen from "./get-screen.js";
22
+ import { collectSocialImageFacts } from "./social-image-facts.js";
23
+ /**
24
+ * Which template screen a collection route renders through (AGL-551):
25
+ * `/{collection}` uses `listScreenId`, `/{collection}/{entry}` uses
26
+ * `entryScreenId` — falling back to the legacy AGL-105 `templateScreenId`
27
+ * so existing blogs keep rendering. `undefined` means no template is set
28
+ * and the designed built-in fallback applies.
29
+ */ export function resolveCollectionTemplateScreenId(collection, kind) {
30
+ if (kind === 'list') return collection.listScreenId || undefined;
31
+ return collection.entryScreenId || collection.templateScreenId || undefined;
32
+ }
33
+ /**
34
+ * Page-level `{{collection.*}}` tokens for template screens (AGL-551), plus
35
+ * the routed category (AGL-1321) so a list template can name what it is
36
+ * showing — "Guides" rather than a heading that says "Blog" on every filtered
37
+ * URL. Both category tokens resolve to the empty string on the unfiltered
38
+ * listing, which is what makes them safe to bind unconditionally.
39
+ *
40
+ * `{{pagination.*}}` follows the same design (AGL-1386). One static list
41
+ * screen serves the bare listing, every `/page/{n}` and every
42
+ * `/category/{slug}`, so a hand-built pager on it renders identically on all
43
+ * of them: "Older →" on a category that has one page, pointing at a URL that
44
+ * dropped the filter. The URLs come from `collectionPaginationLinks`, which
45
+ * builds them through the shared listing-URL builder (so they carry the
46
+ * category) and resolves the EDGES TO THE EMPTY STRING — no previous page,
47
+ * no `prevUrl`. There is no runtime conditional to hide a link with (the
48
+ * `condition` field on nodes is editor-side field visibility, not a render
49
+ * gate), so the empty string is what makes an unconditional binding correct
50
+ * on every route: a link whose href does not resolve renders as an inert
51
+ * placeholder of the same element (AGL-1268/1357).
52
+ *
53
+ * This SURFACES what the platform already computes — the built-in fallback
54
+ * pager reads the same function, so the two cannot drift.
55
+ */ export function collectionTokens(collection, category, pagination) {
56
+ var _ref, _ref1;
57
+ // An unpaginated listing is page 1 of 1 — both URLs empty, which reads as
58
+ // the honest "nowhere to page to" rather than a broken link.
59
+ const pager = Aglyn.collectionPaginationLinks(_extends({
60
+ collectionSlug: collection.slug
61
+ }, (category == null ? void 0 : category.slug) ? {
62
+ categorySlug: category.slug
63
+ } : {}, {
64
+ page: pagination == null ? void 0 : pagination.page,
65
+ totalPages: pagination == null ? void 0 : pagination.totalPages
66
+ }));
67
+ return {
68
+ 'collection.name': collection.displayName,
69
+ 'collection.slug': collection.slug,
70
+ 'collection.category': (_ref = category == null ? void 0 : category.name) != null ? _ref : '',
71
+ 'collection.categorySlug': (_ref1 = category == null ? void 0 : category.slug) != null ? _ref1 : '',
72
+ 'pagination.page': String(pager.page),
73
+ 'pagination.totalPages': String(pager.totalPages),
74
+ 'pagination.prevUrl': pager.prevUrl,
75
+ 'pagination.nextUrl': pager.nextUrl
76
+ };
77
+ }
78
+ /**
79
+ * Renders a collection route through its designated template screen
80
+ * (AGL-551), the same mechanism as commerce PDP/collection templates: the
81
+ * screen composes through the NORMAL published pipeline — theme, shared
82
+ * layout, reusable components — with `{{entry.*}}`/`{{collection.*}}`
83
+ * tokens substituted and Collection entries blocks expanded. Returns null
84
+ * when no template is designated (or it fails to compose) so the caller
85
+ * falls through to the designed built-in fallback.
86
+ */ export async function composeCollectionTemplatePage(options) {
87
+ var _templateRes_screen_seo;
88
+ var _templateRes_screen_seo1, _options_host_seo, _options_host, _content_pagination;
89
+ const { hostId, content } = options;
90
+ const collection = content.collection;
91
+ if (!collection) return null;
92
+ const kind = content.entry ? 'entry' : 'list';
93
+ const screenId = resolveCollectionTemplateScreenId(collection, kind);
94
+ if (!screenId) return null;
95
+ // The one read that WANTS a template document (AGL-1400): this is the
96
+ // composition it exists for, with `{{entry.*}}` substituted against the
97
+ // routed entry. Every path-resolving caller leaves the flag off and gets the
98
+ // 404 a template deserves at an address of its own.
99
+ const templateRes = await getScreen({
100
+ hostId,
101
+ screenId,
102
+ allowTemplate: true
103
+ });
104
+ if (!templateRes.screen) return null;
105
+ const entry = content.entry;
106
+ const tokens = entry ? _extends({}, collectionTokens(collection), Aglyn.collectionEntryTokens(entry, collection.slug, collection.categories)) : collectionTokens(collection, content.category, content.pagination);
107
+ // The head's card on this page, in its order: the entry's cover, the
108
+ // template's own image, then the site default (AGL-2850).
109
+ const card = collectSocialImageFacts([
110
+ entry == null ? void 0 : entry.coverImage,
111
+ (_templateRes_screen_seo1 = templateRes.screen.seo) == null ? void 0 : _templateRes_screen_seo1.image,
112
+ (_options_host = options.host) == null ? void 0 : (_options_host_seo = _options_host.seo) == null ? void 0 : _options_host_seo.image
113
+ ]);
114
+ const nodes = await composeScreenNodes({
115
+ hostId,
116
+ screenId,
117
+ screen: templateRes.screen,
118
+ socialImages: card.socialImages,
119
+ // The site the template renders for, so its host variables — and its
120
+ // layout's — fill in as they do on every other page (AGL-2883).
121
+ host: options.host,
122
+ tokens,
123
+ // List pages hand their already-fetched entries to the Collection
124
+ // entries block; entry pages carry the routed entry (AGL-582, Related
125
+ // posts) and let blocks fetch entry lists on demand (e.g. a "More
126
+ // posts" section on the article template). The category taxonomy
127
+ // rides along so `{{entry.category}}` resolves inside the blocks.
128
+ collection: entry ? {
129
+ slug: collection.slug,
130
+ entry,
131
+ categories: collection.categories
132
+ } : _extends({
133
+ slug: collection.slug,
134
+ // Already filtered to the routed category (AGL-1321) — the block
135
+ // repeats what the ROUTE resolved, so a designer-pinned
136
+ // `filterCategory` on the block narrows it further rather than
137
+ // fighting it.
138
+ entries: content.entries,
139
+ categories: collection.categories
140
+ }, ((_content_pagination = content.pagination) == null ? void 0 : _content_pagination.page) ? {
141
+ page: content.pagination.page
142
+ } : {}, content.category ? {
143
+ categorySlug: content.category.slug
144
+ } : {}, content.entriesReachedBound ? {
145
+ entriesReachedBound: true
146
+ } : {})
147
+ });
148
+ if (!nodes) return null;
149
+ const screenSeo = (_templateRes_screen_seo = templateRes.screen.seo) != null ? _templateRes_screen_seo : {};
150
+ const seo = entry ? _extends({}, screenSeo, {
151
+ title: entry.seoTitle || entry.title,
152
+ description: entry.seoDescription || entry.excerpt || undefined
153
+ }, entry.coverImage ? {
154
+ image: entry.coverImage,
155
+ imageWidth: undefined,
156
+ imageHeight: undefined,
157
+ imageAlt: entry.coverImageAlt || undefined
158
+ } : {
159
+ image: screenSeo.image || undefined
160
+ }) : //
161
+ // This used to default `title` to `collection.displayName`, which reads
162
+ // like a harmless fallback and is not: it made an authored title
163
+ // indistinguishable from a generated one by the time the head was built.
164
+ // The title rule (AGL-1341) turns on exactly that distinction — an
165
+ // authored title renders VERBATIM, a name joins the site title — so a
166
+ // consumer reading this could only choose between dropping the site
167
+ // title off every untitled list ("Changelog") or ignoring the author's
168
+ // title on every titled one ("Changelog – Acme" over the sentence they
169
+ // wrote). The collection name is still the fallback; it just belongs to
170
+ // the title resolver, as the page's `name`, alongside every other
171
+ // surface's fallback rather than baked into stored SEO here.
172
+ screenSeo;
173
+ return _extends({
174
+ screen: _extends({}, templateRes.screen, {
175
+ seo
176
+ }),
177
+ nodes
178
+ }, card.collected());
179
+ }
180
+ /**
181
+ * The designed built-in rendering (AGL-551): when a collection has no
182
+ * template screen, its routes still compose through the site's theme and
183
+ * the host's default shared layout (the home screen's layout) instead of
184
+ * the old unthemed article. Fail-open — any error returns null and the
185
+ * caller keeps the legacy plain rendering.
186
+ */ export async function composeCollectionFallbackPage(options) {
187
+ const { hostId, host, content } = options;
188
+ const collection = content.collection;
189
+ if (!collection) return null;
190
+ try {
191
+ var _content_entry, _host_seo, _content_pagination;
192
+ // The layout for a page the platform composed rather than the author
193
+ // (AGL-2513). Still the home screen's layout by default — the rule this
194
+ // branch has always followed — but the host can now name a different one,
195
+ // and site search reads the same setting so the two built-in pages of a
196
+ // site cannot end up in different chrome.
197
+ const layoutId = await resolveBuiltInPageLayoutId({
198
+ hostId,
199
+ host
200
+ });
201
+ const screenNodes = buildCollectionFallbackNodes({
202
+ collection,
203
+ entries: content.entries,
204
+ entry: content.entry,
205
+ pagination: content.pagination,
206
+ category: content.category,
207
+ // The cover resolves through `resolveMediaSrc` (AGL-1407), and an
208
+ // org-scoped reference has to name the site asking or a site-restricted
209
+ // asset will not serve.
210
+ hostId
211
+ });
212
+ // The head's card on a page with no template: the entry's cover, then the
213
+ // site default (AGL-2850).
214
+ const card = collectSocialImageFacts([
215
+ (_content_entry = content.entry) == null ? void 0 : _content_entry.coverImage,
216
+ host == null ? void 0 : (_host_seo = host.seo) == null ? void 0 : _host_seo.image
217
+ ]);
218
+ const nodes = await composeNodesWithChrome({
219
+ hostId,
220
+ layoutId,
221
+ screenNodes,
222
+ socialImages: card.socialImages,
223
+ // The layout's host variables fill in from this site (AGL-2883).
224
+ host,
225
+ // Entry routes resolve with an EMPTY entries list (the loader only
226
+ // fetched the one entry), so hand the routed entry over and let the
227
+ // Related posts block fetch the list on demand (AGL-582); list
228
+ // routes keep their already-fetched entries. Categories ride along
229
+ // for `categoryId` → name resolution.
230
+ collection: content.entry ? {
231
+ slug: collection.slug,
232
+ entry: content.entry,
233
+ categories: collection.categories
234
+ } : _extends({
235
+ slug: collection.slug,
236
+ entries: content.entries,
237
+ categories: collection.categories
238
+ }, ((_content_pagination = content.pagination) == null ? void 0 : _content_pagination.page) ? {
239
+ page: content.pagination.page
240
+ } : {}, content.category ? {
241
+ categorySlug: content.category.slug
242
+ } : {}, content.entriesReachedBound ? {
243
+ entriesReachedBound: true
244
+ } : {})
245
+ });
246
+ return nodes ? _extends({
247
+ nodes
248
+ }, card.collected()) : null;
249
+ } catch (error) {
250
+ console.error(error);
251
+ return null;
252
+ }
253
+ }
254
+ export default composeCollectionTemplatePage;
255
+
256
+ //# sourceMappingURL=compose-collection-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/compose-collection-page.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 * as Aglyn from '@aglyn/aglyn/server'\nimport buildCollectionFallbackNodes from './collection-fallback-nodes'\nimport composeScreenNodes, {\n composeNodesWithChrome,\n} from './compose-screen-nodes'\nimport { resolveBuiltInPageLayoutId } from './built-in-page-layout'\nimport type { CollectionContent } from './get-collection-content'\nimport getScreen from './get-screen'\nimport { collectSocialImageFacts } from './social-image-facts'\n\ntype CollectionDoc = NonNullable<CollectionContent['collection']>\n\n/**\n * Which template screen a collection route renders through (AGL-551):\n * `/{collection}` uses `listScreenId`, `/{collection}/{entry}` uses\n * `entryScreenId` — falling back to the legacy AGL-105 `templateScreenId`\n * so existing blogs keep rendering. `undefined` means no template is set\n * and the designed built-in fallback applies.\n */\nexport function resolveCollectionTemplateScreenId(\n collection: Pick<\n CollectionDoc,\n 'listScreenId' | 'entryScreenId' | 'templateScreenId'\n >,\n kind: 'list' | 'entry',\n): string | undefined {\n if (kind === 'list') return collection.listScreenId || undefined\n return collection.entryScreenId || collection.templateScreenId || undefined\n}\n\n/**\n * Page-level `{{collection.*}}` tokens for template screens (AGL-551), plus\n * the routed category (AGL-1321) so a list template can name what it is\n * showing — \"Guides\" rather than a heading that says \"Blog\" on every filtered\n * URL. Both category tokens resolve to the empty string on the unfiltered\n * listing, which is what makes them safe to bind unconditionally.\n *\n * `{{pagination.*}}` follows the same design (AGL-1386). One static list\n * screen serves the bare listing, every `/page/{n}` and every\n * `/category/{slug}`, so a hand-built pager on it renders identically on all\n * of them: \"Older →\" on a category that has one page, pointing at a URL that\n * dropped the filter. The URLs come from `collectionPaginationLinks`, which\n * builds them through the shared listing-URL builder (so they carry the\n * category) and resolves the EDGES TO THE EMPTY STRING — no previous page,\n * no `prevUrl`. There is no runtime conditional to hide a link with (the\n * `condition` field on nodes is editor-side field visibility, not a render\n * gate), so the empty string is what makes an unconditional binding correct\n * on every route: a link whose href does not resolve renders as an inert\n * placeholder of the same element (AGL-1268/1357).\n *\n * This SURFACES what the platform already computes — the built-in fallback\n * pager reads the same function, so the two cannot drift.\n */\nexport function collectionTokens(\n collection: Pick<CollectionDoc, 'displayName' | 'slug'>,\n category?: CollectionContent['category'],\n pagination?: CollectionContent['pagination'],\n): Record<string, string> {\n // An unpaginated listing is page 1 of 1 — both URLs empty, which reads as\n // the honest \"nowhere to page to\" rather than a broken link.\n const pager = Aglyn.collectionPaginationLinks({\n collectionSlug: collection.slug,\n ...(category?.slug ? { categorySlug: category.slug } : {}),\n page: pagination?.page,\n totalPages: pagination?.totalPages,\n })\n return {\n 'collection.name': collection.displayName,\n 'collection.slug': collection.slug,\n 'collection.category': category?.name ?? '',\n 'collection.categorySlug': category?.slug ?? '',\n 'pagination.page': String(pager.page),\n 'pagination.totalPages': String(pager.totalPages),\n 'pagination.prevUrl': pager.prevUrl,\n 'pagination.nextUrl': pager.nextUrl,\n }\n}\n\nexport interface ComposedCollectionPage {\n /** Template screen doc with the entry/collection SEO merged in. */\n screen: Record<string, any>\n nodes: Record<string, any>\n /**\n * The current pair of each asset the head's card may name (AGL-2850): the\n * entry's cover, the template's image, the site default. Read in the\n * composition's batch, and absent when it answered for none of them.\n */\n socialImageFacts?: Aglyn.SocialImageAssetFacts\n}\n\n/**\n * Renders a collection route through its designated template screen\n * (AGL-551), the same mechanism as commerce PDP/collection templates: the\n * screen composes through the NORMAL published pipeline — theme, shared\n * layout, reusable components — with `{{entry.*}}`/`{{collection.*}}`\n * tokens substituted and Collection entries blocks expanded. Returns null\n * when no template is designated (or it fails to compose) so the caller\n * falls through to the designed built-in fallback.\n */\nexport async function composeCollectionTemplatePage(options: {\n hostId: string\n content: CollectionContent\n /**\n * The site, whose default card the head falls back to after the entry's\n * cover and the template's image (AGL-2850). Its document is read in this\n * page's batch with theirs.\n */\n host?: Aglyn.AglynHost | null\n}): Promise<ComposedCollectionPage | null> {\n const { hostId, content } = options\n const collection = content.collection\n if (!collection) return null\n const kind = content.entry ? 'entry' : 'list'\n const screenId = resolveCollectionTemplateScreenId(collection, kind)\n if (!screenId) return null\n\n // The one read that WANTS a template document (AGL-1400): this is the\n // composition it exists for, with `{{entry.*}}` substituted against the\n // routed entry. Every path-resolving caller leaves the flag off and gets the\n // 404 a template deserves at an address of its own.\n const templateRes = await getScreen({ hostId, screenId, allowTemplate: true })\n if (!templateRes.screen) return null\n\n const entry = content.entry\n const tokens = entry\n ? {\n ...collectionTokens(collection),\n // Category names resolve against the collection's taxonomy\n // (AGL-582): `categoryId` lookup first, legacy string fallback.\n ...Aglyn.collectionEntryTokens(\n entry,\n collection.slug,\n collection.categories,\n ),\n }\n : collectionTokens(collection, content.category, content.pagination)\n // The head's card on this page, in its order: the entry's cover, the\n // template's own image, then the site default (AGL-2850).\n const card = collectSocialImageFacts([\n entry?.coverImage,\n (templateRes.screen as any).seo?.image,\n options.host?.seo?.image,\n ])\n const nodes = await composeScreenNodes({\n hostId,\n screenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // The site the template renders for, so its host variables — and its\n // layout's — fill in as they do on every other page (AGL-2883).\n host: options.host,\n tokens,\n // List pages hand their already-fetched entries to the Collection\n // entries block; entry pages carry the routed entry (AGL-582, Related\n // posts) and let blocks fetch entry lists on demand (e.g. a \"More\n // posts\" section on the article template). The category taxonomy\n // rides along so `{{entry.category}}` resolves inside the blocks.\n collection: entry\n ? { slug: collection.slug, entry, categories: collection.categories }\n : {\n slug: collection.slug,\n // Already filtered to the routed category (AGL-1321) — the block\n // repeats what the ROUTE resolved, so a designer-pinned\n // `filterCategory` on the block narrows it further rather than\n // fighting it.\n entries: content.entries,\n categories: collection.categories,\n ...(content.pagination?.page\n ? { page: content.pagination.page }\n : {}),\n ...(content.category ? { categorySlug: content.category.slug } : {}),\n // The read's own bound (AGL-1516), which has to travel WITH the\n // filtered entries above: once `content.entries` has been narrowed\n // to a category, nothing downstream can tell a complete read from a\n // truncated one by counting it.\n ...(content.entriesReachedBound\n ? { entriesReachedBound: true }\n : {}),\n },\n })\n if (!nodes) return null\n\n const screenSeo = (templateRes.screen as any).seo ?? {}\n const seo = entry\n ? // Entry metadata drives the head (AGL-117 merge; AGL-582 overrides).\n {\n ...screenSeo,\n title: entry.seoTitle || entry.title,\n description: entry.seoDescription || entry.excerpt || undefined,\n // The image and its companions move as ONE group (AGL-2417). The\n // spread above carries the SCREEN's `imageWidth`/`imageHeight`/\n // `imageAlt`, so an entry that supplies its own cover was describing\n // it with the screen default's size and — once alts existed — with\n // the screen default's DESCRIPTION: a sentence about a picture this\n // card does not show, delivered to the reader least able to check.\n // When the entry wins, its own companions win with it.\n ...(entry.coverImage\n ? {\n image: entry.coverImage,\n imageWidth: undefined,\n imageHeight: undefined,\n imageAlt: entry.coverImageAlt || undefined,\n }\n : { image: screenSeo.image || undefined }),\n }\n : // A LIST passes its screen's own SEO through UNTOUCHED (AGL-1345).\n //\n // This used to default `title` to `collection.displayName`, which reads\n // like a harmless fallback and is not: it made an authored title\n // indistinguishable from a generated one by the time the head was built.\n // The title rule (AGL-1341) turns on exactly that distinction — an\n // authored title renders VERBATIM, a name joins the site title — so a\n // consumer reading this could only choose between dropping the site\n // title off every untitled list (\"Changelog\") or ignoring the author's\n // title on every titled one (\"Changelog – Acme\" over the sentence they\n // wrote). The collection name is still the fallback; it just belongs to\n // the title resolver, as the page's `name`, alongside every other\n // surface's fallback rather than baked into stored SEO here.\n screenSeo\n return {\n screen: { ...(templateRes.screen as any), seo },\n nodes,\n ...card.collected(),\n }\n}\n\n/**\n * The designed built-in rendering (AGL-551): when a collection has no\n * template screen, its routes still compose through the site's theme and\n * the host's default shared layout (the home screen's layout) instead of\n * the old unthemed article. Fail-open — any error returns null and the\n * caller keeps the legacy plain rendering.\n */\nexport async function composeCollectionFallbackPage(options: {\n hostId: string\n host: Aglyn.AglynHost\n content: CollectionContent\n}): Promise<Omit<ComposedCollectionPage, 'screen'> | null> {\n const { hostId, host, content } = options\n const collection = content.collection\n if (!collection) return null\n try {\n // The layout for a page the platform composed rather than the author\n // (AGL-2513). Still the home screen's layout by default — the rule this\n // branch has always followed — but the host can now name a different one,\n // and site search reads the same setting so the two built-in pages of a\n // site cannot end up in different chrome.\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host })\n const screenNodes = buildCollectionFallbackNodes({\n collection,\n entries: content.entries,\n entry: content.entry,\n pagination: content.pagination,\n category: content.category,\n // The cover resolves through `resolveMediaSrc` (AGL-1407), and an\n // org-scoped reference has to name the site asking or a site-restricted\n // asset will not serve.\n hostId,\n })\n // The head's card on a page with no template: the entry's cover, then the\n // site default (AGL-2850).\n const card = collectSocialImageFacts([\n content.entry?.coverImage,\n host?.seo?.image,\n ])\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes,\n socialImages: card.socialImages,\n // The layout's host variables fill in from this site (AGL-2883).\n host,\n // Entry routes resolve with an EMPTY entries list (the loader only\n // fetched the one entry), so hand the routed entry over and let the\n // Related posts block fetch the list on demand (AGL-582); list\n // routes keep their already-fetched entries. Categories ride along\n // for `categoryId` → name resolution.\n collection: content.entry\n ? {\n slug: collection.slug,\n entry: content.entry,\n categories: collection.categories,\n }\n : {\n slug: collection.slug,\n entries: content.entries,\n categories: collection.categories,\n ...(content.pagination?.page\n ? { page: content.pagination.page }\n : {}),\n ...(content.category\n ? { categorySlug: content.category.slug }\n : {}),\n // Same fact, same reason as the template path above (AGL-1516).\n ...(content.entriesReachedBound\n ? { entriesReachedBound: true }\n : {}),\n },\n })\n return nodes ? { nodes, ...card.collected() } : null\n } catch (error) {\n console.error(error)\n return null\n }\n}\n\nexport default composeCollectionTemplatePage\n"],"names":["Aglyn","buildCollectionFallbackNodes","composeScreenNodes","composeNodesWithChrome","resolveBuiltInPageLayoutId","getScreen","collectSocialImageFacts","resolveCollectionTemplateScreenId","collection","kind","listScreenId","undefined","entryScreenId","templateScreenId","collectionTokens","category","pagination","pager","collectionPaginationLinks","collectionSlug","slug","categorySlug","page","totalPages","displayName","name","String","prevUrl","nextUrl","composeCollectionTemplatePage","options","content","hostId","entry","screenId","templateRes","allowTemplate","screen","tokens","collectionEntryTokens","categories","card","coverImage","seo","image","host","nodes","socialImages","entries","entriesReachedBound","screenSeo","title","seoTitle","description","seoDescription","excerpt","imageWidth","imageHeight","imageAlt","coverImageAlt","collected","composeCollectionFallbackPage","layoutId","screenNodes","error","console"],"mappings":"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"}
@@ -0,0 +1,156 @@
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 * as Aglyn from '@aglyn/aglyn/server';
18
+ import { type ComposeSocialImages } from './social-image-facts';
19
+ /**
20
+ * Content-collection context for a compose (AGL-551): the collection the
21
+ * route resolved (list/entry template screens). `entries` rides along when
22
+ * the route already fetched them (list pages); blocks bound to other
23
+ * collections — or to this one when `entries` is absent — fetch on demand.
24
+ */
25
+ export interface ComposeCollectionContext {
26
+ slug: string;
27
+ entries?: Aglyn.CollectionEntryRecord[];
28
+ /**
29
+ * The entry being rendered (AGL-582, entry-template screens / entry
30
+ * fallback) — the Related posts block resolves against it.
31
+ */
32
+ entry?: Aglyn.CollectionEntryRecord | null;
33
+ /**
34
+ * The routed collection's category taxonomy (AGL-582): entry
35
+ * `categoryId`s resolve to display names against it during expansion.
36
+ */
37
+ categories?: Aglyn.CollectionCategory[];
38
+ /**
39
+ * The list page the URL asked for (AGL-1321). Fills in an entries block
40
+ * that declares `perPage` but no `page` — design time cannot know which
41
+ * page a visitor is on.
42
+ */
43
+ page?: number;
44
+ /**
45
+ * The category segment the URL filtered on (AGL-1321); marks the current
46
+ * pill in a Category Pills block. `entries` arrives already filtered.
47
+ */
48
+ categorySlug?: string;
49
+ /**
50
+ * Whether the read behind `entries` stopped at its `.limit()` (AGL-1516).
51
+ * Travels with the entries because the search boxes downstream have to say
52
+ * what they actually searched, and — with the liveness gate and the route's
53
+ * category filter both in between — `entries.length` no longer tells them.
54
+ */
55
+ entriesReachedBound?: boolean;
56
+ /**
57
+ * Whether {@link slug} is a cache KEY rather than an address (AGL-2524).
58
+ *
59
+ * The author page mixes collections, and the compose pipeline keys entry
60
+ * sources by collection slug — so it hands over a synthetic one
61
+ * (`AUTHOR_ENTRIES_SOURCE_SLUG`) with its entries already in hand. That is
62
+ * harmless for the entries block, whose every row carries its OWN
63
+ * `collectionSlug` and builds `entry.url` from it, and for the search box,
64
+ * whose index is built the same way.
65
+ *
66
+ * It is NOT harmless for anything that builds a URL from the source's slug
67
+ * itself. Set this and such a block resolves nothing rather than pointing
68
+ * readers at `/{synthetic}/…`.
69
+ */
70
+ routeless?: boolean;
71
+ }
72
+ /**
73
+ * Shared post-version composition (AGL-551, extracted from
74
+ * `composeScreenNodes`): layout chrome, reusable components, repeatables,
75
+ * collection entries, bindings, function definitions, plugin installs,
76
+ * named tokens, denormalize, and last the current facts of each placed image
77
+ * and film, and of the social card the page is shared as. The screen path and
78
+ * the collection-fallback
79
+ * path (which has no screen doc) build identical trees through this one
80
+ * pipeline.
81
+ */
82
+ export declare function composeNodesWithChrome(options: {
83
+ hostId: string;
84
+ /**
85
+ * The layout binding, or a PROMISE of it.
86
+ *
87
+ * The unresolved form exists for the same reason `screenNodes` accepts one
88
+ * (AGL-1428): the binding may live on the version document (key-present
89
+ * wins over the screen's), and awaiting the version before this call would
90
+ * put every host-scoped read back behind it. Only the layout-chain walk
91
+ * consumes the binding, so it alone waits; the rest of the chrome bundle
92
+ * still starts immediately.
93
+ */
94
+ layoutId?: string | null | Promise<string | null | undefined>;
95
+ /**
96
+ * The screen's values for the properties of the layouts it renders inside
97
+ * (AGL-2893), keyed by layout id — or a PROMISE of them, for the reason
98
+ * `layoutId` accepts one: they live on the version document beside the
99
+ * binding. Absent, every layout renders its properties' defaults.
100
+ */
101
+ layoutPropValues?: Aglyn.AglynScreenVersion['layoutPropValues'] | null | Promise<Aglyn.AglynScreenVersion['layoutPropValues'] | null | undefined>;
102
+ /**
103
+ * The screen's own nodes, or a PROMISE of them (AGL-1428).
104
+ *
105
+ * Accepting the unresolved form is what lets `composeScreenNodes` hand the
106
+ * version read over before it has finished, so the host-scoped reads below
107
+ * — none of which look at these nodes — overlap it instead of queueing
108
+ * behind it. Passing a resolved value behaves exactly as before, which is
109
+ * what every other caller does.
110
+ */
111
+ screenNodes: Record<string, any> | Promise<Record<string, any>>;
112
+ /** Entry-template tokens (AGL-105) substituted before denormalize. */
113
+ tokens?: Record<string, string>;
114
+ /** Routed content collection (AGL-551) for Collection entries blocks. */
115
+ collection?: ComposeCollectionContext;
116
+ /**
117
+ * The host document, for `host.*` tokens (AGL-1022).
118
+ *
119
+ * Passed in rather than read here: every caller already holds it, and
120
+ * `composeScreenNodes` was the largest single render phase before AGL-1225
121
+ * cut it to one round trip — adding a read back would spend that win on
122
+ * data we already have in hand.
123
+ */
124
+ host?: Aglyn.HostTokenSource | null;
125
+ /**
126
+ * The social card the head shares this page as (AGL-2850). The documents of
127
+ * the assets it names are read in the same batch as the images and films
128
+ * the tree places.
129
+ */
130
+ socialImages?: ComposeSocialImages;
131
+ }): Promise<Record<string, any>>;
132
+ /**
133
+ * Full published-render composition for one screen (extracted for AGL-87 so
134
+ * the SSG path and the password-unlock API build identical trees): applies
135
+ * a due publish schedule, loads the version, composes the shared layout
136
+ * chrome, grafts reusable components, and denormalizes.
137
+ */
138
+ export declare function composeScreenNodes(options: {
139
+ hostId: string;
140
+ screenId: string;
141
+ screen: Aglyn.AglynScreen;
142
+ /** Entry-template tokens (AGL-105) substituted before denormalize. */
143
+ tokens?: Record<string, string>;
144
+ /** Routed content collection (AGL-551) for Collection entries blocks. */
145
+ collection?: ComposeCollectionContext;
146
+ /**
147
+ * Compose a specific version instead of the published one (AGL-253):
148
+ * experiment variants point at versions; schedules don't apply.
149
+ */
150
+ versionId?: string;
151
+ /** The host document, for `host.*` tokens (AGL-1022). */
152
+ host?: Aglyn.HostTokenSource | null;
153
+ /** The social card the head shares this page as (AGL-2850). */
154
+ socialImages?: ComposeSocialImages;
155
+ }): Promise<Record<string, any> | null>;
156
+ export default composeScreenNodes;