@aglyn/aglyn 1.0.0-beta.206 → 1.0.0-beta.209

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 (50) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/announce-site-wide-change.d.ts +45 -0
  3. package/src/lib/app-utils/announce-site-wide-change.js +58 -0
  4. package/src/lib/app-utils/announce-site-wide-change.js.map +1 -0
  5. package/src/lib/app-utils/console-routes.d.ts +2 -0
  6. package/src/lib/app-utils/console-routes.js +3 -0
  7. package/src/lib/app-utils/console-routes.js.map +1 -1
  8. package/src/lib/app-utils/content-authors.d.ts +12 -64
  9. package/src/lib/app-utils/content-authors.js +0 -133
  10. package/src/lib/app-utils/content-authors.js.map +1 -1
  11. package/src/lib/app-utils/docs-help.generated.d.ts +6 -0
  12. package/src/lib/app-utils/docs-help.generated.js +61 -0
  13. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  14. package/src/lib/app-utils/docs-index.generated.js +4 -4
  15. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  16. package/src/lib/app-utils/health-report.js +13 -0
  17. package/src/lib/app-utils/health-report.js.map +1 -1
  18. package/src/lib/app-utils/host-transfer.d.ts +55 -0
  19. package/src/lib/app-utils/host-transfer.js +23 -0
  20. package/src/lib/app-utils/host-transfer.js.map +1 -0
  21. package/src/lib/app-utils/local-business.d.ts +161 -0
  22. package/src/lib/app-utils/local-business.js +253 -0
  23. package/src/lib/app-utils/local-business.js.map +1 -0
  24. package/src/lib/app-utils/notifications.d.ts +1 -13
  25. package/src/lib/app-utils/notifications.js +1 -16
  26. package/src/lib/app-utils/notifications.js.map +1 -1
  27. package/src/lib/app-utils/operator-alerts.d.ts +132 -0
  28. package/src/lib/app-utils/operator-alerts.js +513 -0
  29. package/src/lib/app-utils/operator-alerts.js.map +1 -0
  30. package/src/lib/app-utils/screen-link-value.d.ts +4 -0
  31. package/src/lib/app-utils/screen-link-value.js +6 -2
  32. package/src/lib/app-utils/screen-link-value.js.map +1 -1
  33. package/src/lib/app-utils/server.d.ts +1 -0
  34. package/src/lib/app-utils/server.js +9 -0
  35. package/src/lib/app-utils/server.js.map +1 -1
  36. package/src/lib/app-utils/site-entity-json-ld.d.ts +96 -0
  37. package/src/lib/app-utils/site-entity-json-ld.js +220 -0
  38. package/src/lib/app-utils/site-entity-json-ld.js.map +1 -0
  39. package/src/lib/foundation/definitions/platform.types.d.ts +24 -0
  40. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  41. package/src/lib/plugin-manager/feature-plugins.d.ts +9 -0
  42. package/src/lib/plugin-manager/feature-plugins.js +9 -0
  43. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  44. package/src/lib/plugin-manager/first-party-plugins.generated.js +8 -1
  45. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  46. package/src/lib/plugin-manager/operator-alerts.d.ts +37 -0
  47. package/src/lib/plugin-manager/operator-alerts.js +80 -0
  48. package/src/lib/plugin-manager/operator-alerts.js.map +1 -0
  49. package/src/lib/plugin-manager/plugin-host-collections.d.ts +11 -0
  50. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/screen-link-value.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What a stored link VALUE means, with no React attached (AGL-703).\n *\n * Split out of `screen-link-context.ts`, which calls `createContext` at module\n * scope and is therefore excluded from the `@aglyn/aglyn/server` barrel by\n * design (AGL-405). The parsing is not client-only, though, and the where-used\n * scan runs on the server: without this split the API route would have had to\n * re-spell `'screen:'` and its own trimming rules, which is exactly how two\n * readers start disagreeing about what a link points at.\n *\n * `screen-link-context.ts` re-exports every name here, so existing importers —\n * all of which reach these through the `@aglyn/aglyn` barrel — are unaffected.\n *\n * RESOLUTION lives here for the same reason parsing does (AGL-2740). The\n * Markdown representation of a page is built in a route handler, from the\n * server barrel that excludes `screen-link-context.ts` — so with the resolver\n * on the far side of that boundary it grew a second, shorter one, which read\n * only the `screenId` slot and emitted a `screen:<id>` value verbatim when it\n * arrived in the `href` slot. One resolver, reachable from both barrels, is\n * what stops a non-HTML representation from disagreeing with the page.\n */\n\nimport type { ScreenRouteMap } from './screen-link-context-value'\n\n/**\n * Prefix marking a stored link value as a screen REFERENCE rather than a\n * literal href (AGL-1335).\n *\n * A `Link`-typed component prop stores its value in the same string slot\n * whichever way it was authored, and every value written before the picker\n * existed is a raw path (`/pricing`). A bare screen id is indistinguishable\n * from a relative path that happens to have no slash, so the id-carrying\n * shape is the one that gets the marker: a legacy string keeps meaning\n * exactly what it always meant, and only newly picked values indirect\n * through the routing map.\n */\nexport const SCREEN_LINK_VALUE_PREFIX = 'screen:'\n\n/**\n * Prefix marking a stored link value as a content collection's LISTING page\n * (AGL-2799): `collection:<collectionId>` is the page at `/{collectionSlug}`.\n *\n * The listing is addressed by the collection's id and never by its slug, for\n * the reason a screen is: `/blog` is where the listing happens to be today,\n * and the id is the part a rename cannot move.\n *\n * The value is ALSO the listing's key in the linkable routing map\n * (`linkableScreenRoutes`), so every reader that resolves a screen id against\n * that map — `resolveScreenHref`, the broken-link verdict, the Tabs strip, the\n * Markdown representation of a page — resolves a listing through the same\n * lookup, with no second path to fall out of step. Generated ids contain no\n * colon, so the key cannot collide with a screen id in the same map.\n *\n * Unlike {@link SCREEN_LINK_VALUE_PREFIX}, this marker is written in every\n * slot the value can occupy, the screen slot included: a bare collection id\n * there would be read as a screen id.\n */\nexport const COLLECTION_LINK_VALUE_PREFIX = 'collection:'\n\n/** Wraps a collection id as a stored listing link — see {@link COLLECTION_LINK_VALUE_PREFIX}. */\nexport function formatCollectionLinkValue(collectionId: string): string {\n return `${COLLECTION_LINK_VALUE_PREFIX}${collectionId.trim()}`\n}\n\n/**\n * The collection id a stored listing link names, or `undefined` for any value\n * that is not a listing link.\n */\nexport function parseCollectionLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(COLLECTION_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(COLLECTION_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/**\n * Prefix marking a stored link value as a content collection ENTRY (AGL-3118):\n * `entry:<collectionId>/<entryId>` is the page at `/{collectionSlug}/{entrySlug}`.\n *\n * Both halves are ids, for the reason a listing is addressed by its\n * collection's id: an entry's slug and its collection's slug can each be\n * renamed, and the id pair is the part neither rename moves. A link written\n * once follows both.\n *\n * Like the listing form, the value is the stored form in every slot AND the\n * entry's key in the linkable routing map, so `resolveScreenHref` resolves an\n * entry through the same lookup as a screen. Generated ids contain neither a\n * colon nor a slash, so the key cannot collide with a screen id or a listing.\n * Entry keys are not in every map: a page carries the ones it references.\n */\nexport const ENTRY_LINK_VALUE_PREFIX = 'entry:'\n\n/** Wraps an entry's ids as a stored entry link — see {@link ENTRY_LINK_VALUE_PREFIX}. */\nexport function formatEntryLinkValue(collectionId: string, entryId: string): string {\n return `${ENTRY_LINK_VALUE_PREFIX}${collectionId.trim()}/${entryId.trim()}`\n}\n\n/**\n * The collection and entry ids a stored entry link names, or `undefined` for\n * any value that is not exactly one well-formed entry link.\n */\nexport function parseEntryLinkValue(\n value: unknown,\n): { collectionId: string; entryId: string } | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(ENTRY_LINK_VALUE_PREFIX)) return undefined\n const parts = trimmed\n .slice(ENTRY_LINK_VALUE_PREFIX.length)\n .split('/')\n .map((part) => part.trim())\n if (parts.length !== 2) return undefined\n const [collectionId, entryId] = parts\n if (!collectionId || !entryId) return undefined\n return { collectionId, entryId }\n}\n\n/**\n * Prefix marking a stored link value as a content collection's RSS FEED\n * (AGL-3118): `feed:<collectionId>` is `/{collectionSlug}/rss.xml`.\n *\n * Addressed by the collection's id for the listing's reason, and keyed in the\n * linkable routing map beside the listing it belongs to, so a feed link\n * follows a renamed collection exactly as its listing link does.\n */\nexport const FEED_LINK_VALUE_PREFIX = 'feed:'\n\n/** Wraps a collection id as a stored feed link — see {@link FEED_LINK_VALUE_PREFIX}. */\nexport function formatFeedLinkValue(collectionId: string): string {\n return `${FEED_LINK_VALUE_PREFIX}${collectionId.trim()}`\n}\n\n/**\n * The collection id a stored feed link names, or `undefined` for any value\n * that is not a feed link.\n */\nexport function parseFeedLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(FEED_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(FEED_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/** What a link target key names: a screen, or one of a collection's pages. */\nexport type LinkTargetKind = 'screen' | 'collection' | 'entry' | 'feed'\n\n/**\n * The kind of a routing-map key or stored link value. Anything that is not a\n * listing, entry or feed reference is a screen, which is what a bare id in\n * the map always was.\n */\nexport function linkTargetKind(key: string): LinkTargetKind {\n if (parseCollectionLinkValue(key) !== undefined) return 'collection'\n if (parseEntryLinkValue(key) !== undefined) return 'entry'\n if (parseFeedLinkValue(key) !== undefined) return 'feed'\n return 'screen'\n}\n\n/**\n * Wraps a link target as a stored value — see {@link SCREEN_LINK_VALUE_PREFIX}.\n *\n * Takes what a picker option carries: a screen id, which gains the marker, or\n * a listing, entry or feed key, which is already its own stored form and comes\n * back unchanged.\n */\nexport function formatScreenLinkValue(screenId: string): string {\n const collectionId = parseCollectionLinkValue(screenId)\n if (collectionId) return formatCollectionLinkValue(collectionId)\n const entry = parseEntryLinkValue(screenId)\n if (entry) return formatEntryLinkValue(entry.collectionId, entry.entryId)\n const feedCollectionId = parseFeedLinkValue(screenId)\n if (feedCollectionId) return formatFeedLinkValue(feedCollectionId)\n return `${SCREEN_LINK_VALUE_PREFIX}${screenId}`\n}\n\n/**\n * The routing-map key a stored link value references — a screen id, or a\n * listing's `collection:<id>`, an entry's `entry:<collectionId>/<entryId>` or\n * a feed's `feed:<id>` — or `undefined` when the value is a literal href\n * (legacy raw string, external URL, or unset).\n */\nexport function parseScreenLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n const collectionId = parseCollectionLinkValue(trimmed)\n if (collectionId) return formatCollectionLinkValue(collectionId)\n const entry = parseEntryLinkValue(trimmed)\n if (entry) return formatEntryLinkValue(entry.collectionId, entry.entryId)\n const feedCollectionId = parseFeedLinkValue(trimmed)\n if (feedCollectionId) return formatFeedLinkValue(feedCollectionId)\n if (!trimmed.startsWith(SCREEN_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(SCREEN_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/**\n * Whether a routing map can say anything about this link target (AGL-1893,\n * AGL-2799, AGL-3118).\n *\n * The map holds several kinds of key read from different places — screens\n * from the host document, listings and feeds from the host's collections,\n * entries from whatever a page references — and any half can be empty while\n * another is not: a console that has received one subscription and not yet\n * the other, a site with a blog and no published screen, or a page that\n * references no entry at all. A map with no key of the target's OWN kind has\n * not heard of that kind, so it is no evidence the target is gone.\n *\n * Compared per kind rather than as \"the map is non-empty\" because the loading\n * beat is exactly where the difference shows: the first collection to arrive\n * would otherwise condemn every screen link on the canvas until the host\n * document caught up, and the host document would condemn every listing link\n * until the collections did.\n */\nexport function screenRoutesAnswerFor(\n screens: ScreenRouteMap | undefined,\n target: string | null | undefined,\n): boolean {\n if (!screens || !target) return false\n const key = parseScreenLinkValue(target) ?? target.trim()\n const wanted = linkTargetKind(key)\n for (const candidate in screens) {\n if (linkTargetKind(candidate) === wanted) return true\n }\n return false\n}\n\n/**\n * Navigable protocols only. A stored `javascript:`/`data:` href would\n * execute in visitors' browsers, so the guard the linking components each\n * carried is here instead — one copy, one place to harden.\n */\nexport const SAFE_HREF_PATTERN = /^(https?:\\/\\/|mailto:|tel:|\\/|#)/i\n\n/** Of those, the ones that actually leave the site (new-tab decisions). */\nexport const EXTERNAL_HREF_PATTERN = /^(https?:\\/\\/|mailto:|tel:)/i\n\n/**\n * The id a link value jumps to when a fragment is ALL it holds — `#watch`\n * gives `watch` — or `undefined` for any other value (AGL-2867).\n *\n * A link field takes paths, so it takes `#watch` too, and on the canvas the\n * element looks linked. On the published page the browser looks for an\n * element with that id, and the elements an author places carry no id they\n * can set, so the press goes nowhere. Two fragments are not that trap and are\n * left out: `#top` (any case), which a browser scrolls to the top of the page\n * with no element needed, and a value holding a `{{…}}` binding, whose text is\n * not known until the page renders. A lone `#` names no element either, and\n * means the top of the page.\n */\nexport function bareFragmentOfLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith('#') || trimmed.includes('{{')) return undefined\n const fragment = trimmed.slice(1)\n if (!fragment || fragment.toLowerCase() === 'top') return undefined\n return fragment\n}\n\n/**\n * What a link field says while its value is a bare fragment, or `undefined`\n * (AGL-2867). It names the interaction that does what the fragment was\n * reaching for, because the author is looking at the field, not at the docs.\n */\nexport function bareFragmentLinkWarning(value: unknown): string | undefined {\n const fragment = bareFragmentOfLinkValue(value)\n if (fragment === undefined) return undefined\n return (\n `Elements you place carry no id, so #${fragment} goes nowhere on the ` +\n 'published page. To take visitors to an element, clear this and add an ' +\n 'interaction: When clicked → Scroll to element.'\n )\n}\n\n/**\n * {@link bareFragmentLinkWarning} as a form field's `resolveProps`, for a link\n * attribute in a component schema: the field's helper text carries the warning\n * while the value is a bare fragment, and the field's own description\n * otherwise. Computed from the live value, so it appears as the fragment is\n * typed and clears as it is removed.\n */\nexport function bareFragmentLinkFieldProps(\n _props: unknown,\n field: { input?: { value?: unknown } },\n): { helperText?: string } {\n const warning = bareFragmentLinkWarning(field?.input?.value)\n return warning ? { helperText: warning } : {}\n}\n\n/**\n * Turns a screen id into its current href against a routing map, or\n * `undefined` when there is no id or the id has no entry (unpublished or\n * deleted). Pure and hook-free on purpose: an element that resolves ONE\n * target uses `useScreenLink`, but a row of them — the Tabs strip's per-tab\n * links (AGL-1312) — cannot call a hook per item, and the map-to-path\n * contract (root is `'/'`, everything else gains a leading slash) must have\n * exactly one implementation.\n */\nexport function resolveScreenHref(\n screens: ScreenRouteMap | undefined,\n screenId: string | null | undefined,\n): string | undefined {\n if (!screenId) return undefined\n // A value that arrived through a `Link`-typed component prop (AGL-1335)\n // carries the prefix, because there it has to be distinguishable from the\n // raw path strings those props held before the picker existed. Stripping\n // it HERE rather than at each call site is the same \"one resolver\" rule\n // the doc comment above states: every surface that resolves a screen id\n // must accept both spellings, or a prop-fed tab strip would resolve where\n // a prop-fed button did not.\n const id = parseScreenLinkValue(screenId) ?? screenId\n const path = screens?.[id]\n if (path === undefined) return undefined\n return path === '/' ? '/' : `/${path}`\n}\n\n/**\n * Sorts an element's two link inputs into \"a screen id\" and \"a literal\n * href\", tolerating either value arriving in either slot (AGL-1335).\n *\n * Both slots are string props, and a component prop bound with\n * `{{prop.link}}` can be dropped into whichever one the author reached for\n * first. So the ROUTING is driven by the value's shape, not by which field\n * it sits in:\n *\n * - a `screen:`-prefixed value is a screen reference wherever it appears;\n * - an href-shaped value (`/x`, `https://…`, `#a`, `mailto:`) in the screen\n * slot is a literal href — a real screen id never looks like that, and\n * the alternative is a link that silently resolves to nothing;\n * - anything else in the screen slot is a bare screen id, exactly as before.\n *\n * A resolved screen id always wins: `screenId` has taken precedence over\n * `href` since AGL-139, and this must not change which of the two an\n * already-published page follows.\n */\nexport function splitLinkValue(\n screenId: string | null | undefined,\n href: string | null | undefined,\n): { screenId?: string; href?: string } {\n const rawScreen = typeof screenId === 'string' ? screenId.trim() : ''\n const rawHref = typeof href === 'string' ? href.trim() : ''\n const fromScreenSlot = parseScreenLinkValue(rawScreen)\n if (fromScreenSlot) return { screenId: fromScreenSlot }\n if (rawScreen && !SAFE_HREF_PATTERN.test(rawScreen)) {\n return { screenId: rawScreen }\n }\n const fromHrefSlot = parseScreenLinkValue(rawHref)\n if (fromHrefSlot) return { screenId: fromHrefSlot }\n // An href-shaped value in the screen slot beats an empty href slot, and\n // loses to a real one — the screen slot was never meant to hold a path.\n const literal = rawHref || rawScreen\n return literal ? { href: literal } : {}\n}\n\n/**\n * The target half of a markdown-lite link, `[text](target)` — the same shape\n * the parser keeps, so this finds what a reader will be able to click.\n */\nconst MARKDOWN_LINK_TARGET = /\\]\\(([^)\\s]+)\\)/g\n\n/**\n * Whether a stored node tree links to a given screen (AGL-703).\n *\n * DEEP, unlike {@link nodesReferenceComponent} which reads `props.refId` at\n * one known key. A screen id can sit almost anywhere in a prop bag: the\n * `screenId`/`href` pair every linking element declares, a `Link`-typed\n * component prop the author bound to either slot, and — the case a shallow\n * walk would miss entirely — the ITEM ARRAYS a nav strip, a tab set, or a\n * mega menu store their targets in. Those arrays are where a site's\n * navigation actually lives, so a scan that skipped them would report the\n * home page as linked from nowhere.\n *\n * Two accepted spellings, matching {@link splitLinkValue}'s own rules:\n *\n * - `screen:<id>` — the marked form every picked value has written since\n * AGL-1335;\n * - a bare `<id>` — the legacy form, still live on anything authored before\n * the picker.\n *\n * The bare form is the one that could over-match, and it is allowed to. A\n * screen id is a generated 10-character token, so a prop holding that exact\n * string for some unrelated reason is a theoretical case; and this answers\n * \"what might I break\", where naming one extra document costs a second look\n * and missing one costs a dead link on a live site.\n *\n * A MARKDOWN body counts too (AGL-3118). A link written `[text](collection:…)`\n * inside a Markdown element's content, or inside an entry body, renders as a\n * real link on the page, so whatever it names is as genuinely referenced as a\n * link element's `screenId` — and a delete that ignored it would break the\n * sentence silently.\n */\nexport function nodesReferenceScreen(\n nodes: Record<string, unknown> | null | undefined,\n screenId: string,\n): boolean {\n if (!nodes || !screenId) return false\n const matches = (value: unknown): boolean => {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed === screenId || parseScreenLinkValue(trimmed) === screenId) {\n return true\n }\n // Only strings that hold a markdown link are worth parsing, which on a\n // page is almost none of them.\n if (!trimmed.includes('](')) return false\n for (const match of trimmed.matchAll(MARKDOWN_LINK_TARGET)) {\n const target = (match[1] ?? '').trim()\n if (target === screenId || parseScreenLinkValue(target) === screenId) {\n return true\n }\n }\n return false\n }\n if (Array.isArray(value)) return value.some(matches)\n if (value && typeof value === 'object') {\n return Object.values(value as Record<string, unknown>).some(matches)\n }\n return false\n }\n for (const node of Object.values(nodes)) {\n const props = (node as { props?: unknown } | undefined)?.props\n if (props && matches(props)) return true\n }\n return false\n}\n"],"names":["SCREEN_LINK_VALUE_PREFIX","COLLECTION_LINK_VALUE_PREFIX","formatCollectionLinkValue","collectionId","trim","parseCollectionLinkValue","value","undefined","trimmed","startsWith","id","slice","length","ENTRY_LINK_VALUE_PREFIX","formatEntryLinkValue","entryId","parseEntryLinkValue","parts","split","map","part","FEED_LINK_VALUE_PREFIX","formatFeedLinkValue","parseFeedLinkValue","linkTargetKind","key","formatScreenLinkValue","screenId","entry","feedCollectionId","parseScreenLinkValue","screenRoutesAnswerFor","screens","target","wanted","candidate","SAFE_HREF_PATTERN","EXTERNAL_HREF_PATTERN","bareFragmentOfLinkValue","includes","fragment","toLowerCase","bareFragmentLinkWarning","bareFragmentLinkFieldProps","_props","field","warning","input","helperText","resolveScreenHref","path","splitLinkValue","href","rawScreen","rawHref","fromScreenSlot","test","fromHrefSlot","literal","MARKDOWN_LINK_TARGET","nodesReferenceScreen","nodes","matches","match","matchAll","Array","isArray","some","Object","values","node","props"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/screen-link-value.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What a stored link VALUE means, with no React attached (AGL-703).\n *\n * Split out of `screen-link-context.ts`, which calls `createContext` at module\n * scope and is therefore excluded from the `@aglyn/aglyn/server` barrel by\n * design (AGL-405). The parsing is not client-only, though, and the where-used\n * scan runs on the server: without this split the API route would have had to\n * re-spell `'screen:'` and its own trimming rules, which is exactly how two\n * readers start disagreeing about what a link points at.\n *\n * `screen-link-context.ts` re-exports every name here, so existing importers —\n * all of which reach these through the `@aglyn/aglyn` barrel — are unaffected.\n *\n * RESOLUTION lives here for the same reason parsing does (AGL-2740). The\n * Markdown representation of a page is built in a route handler, from the\n * server barrel that excludes `screen-link-context.ts` — so with the resolver\n * on the far side of that boundary it grew a second, shorter one, which read\n * only the `screenId` slot and emitted a `screen:<id>` value verbatim when it\n * arrived in the `href` slot. One resolver, reachable from both barrels, is\n * what stops a non-HTML representation from disagreeing with the page.\n */\n\nimport type { ScreenRouteMap } from './screen-link-context-value'\n\n/**\n * Prefix marking a stored link value as a screen REFERENCE rather than a\n * literal href (AGL-1335).\n *\n * A `Link`-typed component prop stores its value in the same string slot\n * whichever way it was authored, and every value written before the picker\n * existed is a raw path (`/pricing`). A bare screen id is indistinguishable\n * from a relative path that happens to have no slash, so the id-carrying\n * shape is the one that gets the marker: a legacy string keeps meaning\n * exactly what it always meant, and only newly picked values indirect\n * through the routing map.\n */\nexport const SCREEN_LINK_VALUE_PREFIX = 'screen:'\n\n/**\n * Prefix marking a stored link value as a content collection's LISTING page\n * (AGL-2799): `collection:<collectionId>` is the page at `/{collectionSlug}`.\n *\n * The listing is addressed by the collection's id and never by its slug, for\n * the reason a screen is: `/blog` is where the listing happens to be today,\n * and the id is the part a rename cannot move.\n *\n * The value is ALSO the listing's key in the linkable routing map\n * (`linkableScreenRoutes`), so every reader that resolves a screen id against\n * that map — `resolveScreenHref`, the broken-link verdict, the Tabs strip, the\n * Markdown representation of a page — resolves a listing through the same\n * lookup, with no second path to fall out of step. Generated ids contain no\n * colon, so the key cannot collide with a screen id in the same map.\n *\n * Unlike {@link SCREEN_LINK_VALUE_PREFIX}, this marker is written in every\n * slot the value can occupy, the screen slot included: a bare collection id\n * there would be read as a screen id.\n */\nexport const COLLECTION_LINK_VALUE_PREFIX = 'collection:'\n\n/** Wraps a collection id as a stored listing link — see {@link COLLECTION_LINK_VALUE_PREFIX}. */\nexport function formatCollectionLinkValue(collectionId: string): string {\n return `${COLLECTION_LINK_VALUE_PREFIX}${collectionId.trim()}`\n}\n\n/**\n * The collection id a stored listing link names, or `undefined` for any value\n * that is not a listing link.\n */\nexport function parseCollectionLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(COLLECTION_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(COLLECTION_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/**\n * Prefix marking a stored link value as a content collection ENTRY (AGL-3118):\n * `entry:<collectionId>/<entryId>` is the page at `/{collectionSlug}/{entrySlug}`.\n *\n * Both halves are ids, for the reason a listing is addressed by its\n * collection's id: an entry's slug and its collection's slug can each be\n * renamed, and the id pair is the part neither rename moves. A link written\n * once follows both.\n *\n * Like the listing form, the value is the stored form in every slot AND the\n * entry's key in the linkable routing map, so `resolveScreenHref` resolves an\n * entry through the same lookup as a screen. Generated ids contain neither a\n * colon nor a slash, so the key cannot collide with a screen id or a listing.\n * Entry keys are not in every map: a page carries the ones it references.\n */\nexport const ENTRY_LINK_VALUE_PREFIX = 'entry:'\n\n/** Wraps an entry's ids as a stored entry link — see {@link ENTRY_LINK_VALUE_PREFIX}. */\nexport function formatEntryLinkValue(collectionId: string, entryId: string): string {\n return `${ENTRY_LINK_VALUE_PREFIX}${collectionId.trim()}/${entryId.trim()}`\n}\n\n/**\n * The collection and entry ids a stored entry link names, or `undefined` for\n * any value that is not exactly one well-formed entry link.\n */\nexport function parseEntryLinkValue(\n value: unknown,\n): { collectionId: string; entryId: string } | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(ENTRY_LINK_VALUE_PREFIX)) return undefined\n const parts = trimmed\n .slice(ENTRY_LINK_VALUE_PREFIX.length)\n .split('/')\n .map((part) => part.trim())\n if (parts.length !== 2) return undefined\n const [collectionId, entryId] = parts\n if (!collectionId || !entryId) return undefined\n return { collectionId, entryId }\n}\n\n/**\n * Prefix marking a stored link value as a content collection's RSS FEED\n * (AGL-3118): `feed:<collectionId>` is `/{collectionSlug}/rss.xml`.\n *\n * Addressed by the collection's id for the listing's reason, and keyed in the\n * linkable routing map beside the listing it belongs to, so a feed link\n * follows a renamed collection exactly as its listing link does.\n */\nexport const FEED_LINK_VALUE_PREFIX = 'feed:'\n\n/** Wraps a collection id as a stored feed link — see {@link FEED_LINK_VALUE_PREFIX}. */\nexport function formatFeedLinkValue(collectionId: string): string {\n return `${FEED_LINK_VALUE_PREFIX}${collectionId.trim()}`\n}\n\n/**\n * The collection id a stored feed link names, or `undefined` for any value\n * that is not a feed link.\n */\nexport function parseFeedLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith(FEED_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(FEED_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/** What a link target key names: a screen, or one of a collection's pages. */\nexport type LinkTargetKind = 'screen' | 'collection' | 'entry' | 'feed'\n\n/**\n * The kind of a routing-map key or stored link value. Anything that is not a\n * listing, entry or feed reference is a screen, which is what a bare id in\n * the map always was.\n */\nexport function linkTargetKind(key: string): LinkTargetKind {\n if (parseCollectionLinkValue(key) !== undefined) return 'collection'\n if (parseEntryLinkValue(key) !== undefined) return 'entry'\n if (parseFeedLinkValue(key) !== undefined) return 'feed'\n return 'screen'\n}\n\n/**\n * Wraps a link target as a stored value — see {@link SCREEN_LINK_VALUE_PREFIX}.\n *\n * Takes what a picker option carries: a screen id, which gains the marker, or\n * a listing, entry or feed key, which is already its own stored form and comes\n * back unchanged.\n */\nexport function formatScreenLinkValue(screenId: string): string {\n const collectionId = parseCollectionLinkValue(screenId)\n if (collectionId) return formatCollectionLinkValue(collectionId)\n const entry = parseEntryLinkValue(screenId)\n if (entry) return formatEntryLinkValue(entry.collectionId, entry.entryId)\n const feedCollectionId = parseFeedLinkValue(screenId)\n if (feedCollectionId) return formatFeedLinkValue(feedCollectionId)\n return `${SCREEN_LINK_VALUE_PREFIX}${screenId}`\n}\n\n/**\n * The routing-map key a stored link value references — a screen id, or a\n * listing's `collection:<id>`, an entry's `entry:<collectionId>/<entryId>` or\n * a feed's `feed:<id>` — or `undefined` when the value is a literal href\n * (legacy raw string, external URL, or unset).\n */\nexport function parseScreenLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n const collectionId = parseCollectionLinkValue(trimmed)\n if (collectionId) return formatCollectionLinkValue(collectionId)\n const entry = parseEntryLinkValue(trimmed)\n if (entry) return formatEntryLinkValue(entry.collectionId, entry.entryId)\n const feedCollectionId = parseFeedLinkValue(trimmed)\n if (feedCollectionId) return formatFeedLinkValue(feedCollectionId)\n if (!trimmed.startsWith(SCREEN_LINK_VALUE_PREFIX)) return undefined\n const id = trimmed.slice(SCREEN_LINK_VALUE_PREFIX.length).trim()\n return id || undefined\n}\n\n/**\n * Whether a routing map can say anything about this link target (AGL-1893,\n * AGL-2799, AGL-3118).\n *\n * The map holds several kinds of key read from different places — screens\n * from the host document, listings and feeds from the host's collections,\n * entries from whatever a page references — and any half can be empty while\n * another is not: a console that has received one subscription and not yet\n * the other, a site with a blog and no published screen, or a page that\n * references no entry at all. A map with no key of the target's OWN kind has\n * not heard of that kind, so it is no evidence the target is gone.\n *\n * Compared per kind rather than as \"the map is non-empty\" because the loading\n * beat is exactly where the difference shows: the first collection to arrive\n * would otherwise condemn every screen link on the canvas until the host\n * document caught up, and the host document would condemn every listing link\n * until the collections did.\n */\nexport function screenRoutesAnswerFor(\n screens: ScreenRouteMap | undefined,\n target: string | null | undefined,\n): boolean {\n if (!screens || !target) return false\n const key = parseScreenLinkValue(target) ?? target.trim()\n const wanted = linkTargetKind(key)\n for (const candidate in screens) {\n if (linkTargetKind(candidate) === wanted) return true\n }\n return false\n}\n\n/**\n * Navigable protocols only. A stored `javascript:`/`data:` href would\n * execute in visitors' browsers, so the guard the linking components each\n * carried is here instead — one copy, one place to harden.\n *\n * `sms:` sits beside `tel:` (AGL-3388): it opens the visitor's messaging app\n * and executes nothing, the same list `safe-url-scheme.ts` keeps. Leaving it\n * out rendered every \"Text us\" button as a button with no link.\n */\nexport const SAFE_HREF_PATTERN = /^(https?:\\/\\/|mailto:|tel:|sms:|\\/|#)/i\n\n/** Of those, the ones that actually leave the site (new-tab decisions). */\nexport const EXTERNAL_HREF_PATTERN = /^(https?:\\/\\/|mailto:|tel:|sms:)/i\n\n/**\n * The id a link value jumps to when a fragment is ALL it holds — `#watch`\n * gives `watch` — or `undefined` for any other value (AGL-2867).\n *\n * A link field takes paths, so it takes `#watch` too, and on the canvas the\n * element looks linked. On the published page the browser looks for an\n * element with that id, and the elements an author places carry no id they\n * can set, so the press goes nowhere. Two fragments are not that trap and are\n * left out: `#top` (any case), which a browser scrolls to the top of the page\n * with no element needed, and a value holding a `{{…}}` binding, whose text is\n * not known until the page renders. A lone `#` names no element either, and\n * means the top of the page.\n */\nexport function bareFragmentOfLinkValue(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed.startsWith('#') || trimmed.includes('{{')) return undefined\n const fragment = trimmed.slice(1)\n if (!fragment || fragment.toLowerCase() === 'top') return undefined\n return fragment\n}\n\n/**\n * What a link field says while its value is a bare fragment, or `undefined`\n * (AGL-2867). It names the interaction that does what the fragment was\n * reaching for, because the author is looking at the field, not at the docs.\n */\nexport function bareFragmentLinkWarning(value: unknown): string | undefined {\n const fragment = bareFragmentOfLinkValue(value)\n if (fragment === undefined) return undefined\n return (\n `Elements you place carry no id, so #${fragment} goes nowhere on the ` +\n 'published page. To take visitors to an element, clear this and add an ' +\n 'interaction: When clicked → Scroll to element.'\n )\n}\n\n/**\n * {@link bareFragmentLinkWarning} as a form field's `resolveProps`, for a link\n * attribute in a component schema: the field's helper text carries the warning\n * while the value is a bare fragment, and the field's own description\n * otherwise. Computed from the live value, so it appears as the fragment is\n * typed and clears as it is removed.\n */\nexport function bareFragmentLinkFieldProps(\n _props: unknown,\n field: { input?: { value?: unknown } },\n): { helperText?: string } {\n const warning = bareFragmentLinkWarning(field?.input?.value)\n return warning ? { helperText: warning } : {}\n}\n\n/**\n * Turns a screen id into its current href against a routing map, or\n * `undefined` when there is no id or the id has no entry (unpublished or\n * deleted). Pure and hook-free on purpose: an element that resolves ONE\n * target uses `useScreenLink`, but a row of them — the Tabs strip's per-tab\n * links (AGL-1312) — cannot call a hook per item, and the map-to-path\n * contract (root is `'/'`, everything else gains a leading slash) must have\n * exactly one implementation.\n */\nexport function resolveScreenHref(\n screens: ScreenRouteMap | undefined,\n screenId: string | null | undefined,\n): string | undefined {\n if (!screenId) return undefined\n // A value that arrived through a `Link`-typed component prop (AGL-1335)\n // carries the prefix, because there it has to be distinguishable from the\n // raw path strings those props held before the picker existed. Stripping\n // it HERE rather than at each call site is the same \"one resolver\" rule\n // the doc comment above states: every surface that resolves a screen id\n // must accept both spellings, or a prop-fed tab strip would resolve where\n // a prop-fed button did not.\n const id = parseScreenLinkValue(screenId) ?? screenId\n const path = screens?.[id]\n if (path === undefined) return undefined\n return path === '/' ? '/' : `/${path}`\n}\n\n/**\n * Sorts an element's two link inputs into \"a screen id\" and \"a literal\n * href\", tolerating either value arriving in either slot (AGL-1335).\n *\n * Both slots are string props, and a component prop bound with\n * `{{prop.link}}` can be dropped into whichever one the author reached for\n * first. So the ROUTING is driven by the value's shape, not by which field\n * it sits in:\n *\n * - a `screen:`-prefixed value is a screen reference wherever it appears;\n * - an href-shaped value (`/x`, `https://…`, `#a`, `mailto:`) in the screen\n * slot is a literal href — a real screen id never looks like that, and\n * the alternative is a link that silently resolves to nothing;\n * - anything else in the screen slot is a bare screen id, exactly as before.\n *\n * A resolved screen id always wins: `screenId` has taken precedence over\n * `href` since AGL-139, and this must not change which of the two an\n * already-published page follows.\n */\nexport function splitLinkValue(\n screenId: string | null | undefined,\n href: string | null | undefined,\n): { screenId?: string; href?: string } {\n const rawScreen = typeof screenId === 'string' ? screenId.trim() : ''\n const rawHref = typeof href === 'string' ? href.trim() : ''\n const fromScreenSlot = parseScreenLinkValue(rawScreen)\n if (fromScreenSlot) return { screenId: fromScreenSlot }\n if (rawScreen && !SAFE_HREF_PATTERN.test(rawScreen)) {\n return { screenId: rawScreen }\n }\n const fromHrefSlot = parseScreenLinkValue(rawHref)\n if (fromHrefSlot) return { screenId: fromHrefSlot }\n // An href-shaped value in the screen slot beats an empty href slot, and\n // loses to a real one — the screen slot was never meant to hold a path.\n const literal = rawHref || rawScreen\n return literal ? { href: literal } : {}\n}\n\n/**\n * The target half of a markdown-lite link, `[text](target)` — the same shape\n * the parser keeps, so this finds what a reader will be able to click.\n */\nconst MARKDOWN_LINK_TARGET = /\\]\\(([^)\\s]+)\\)/g\n\n/**\n * Whether a stored node tree links to a given screen (AGL-703).\n *\n * DEEP, unlike {@link nodesReferenceComponent} which reads `props.refId` at\n * one known key. A screen id can sit almost anywhere in a prop bag: the\n * `screenId`/`href` pair every linking element declares, a `Link`-typed\n * component prop the author bound to either slot, and — the case a shallow\n * walk would miss entirely — the ITEM ARRAYS a nav strip, a tab set, or a\n * mega menu store their targets in. Those arrays are where a site's\n * navigation actually lives, so a scan that skipped them would report the\n * home page as linked from nowhere.\n *\n * Two accepted spellings, matching {@link splitLinkValue}'s own rules:\n *\n * - `screen:<id>` — the marked form every picked value has written since\n * AGL-1335;\n * - a bare `<id>` — the legacy form, still live on anything authored before\n * the picker.\n *\n * The bare form is the one that could over-match, and it is allowed to. A\n * screen id is a generated 10-character token, so a prop holding that exact\n * string for some unrelated reason is a theoretical case; and this answers\n * \"what might I break\", where naming one extra document costs a second look\n * and missing one costs a dead link on a live site.\n *\n * A MARKDOWN body counts too (AGL-3118). A link written `[text](collection:…)`\n * inside a Markdown element's content, or inside an entry body, renders as a\n * real link on the page, so whatever it names is as genuinely referenced as a\n * link element's `screenId` — and a delete that ignored it would break the\n * sentence silently.\n */\nexport function nodesReferenceScreen(\n nodes: Record<string, unknown> | null | undefined,\n screenId: string,\n): boolean {\n if (!nodes || !screenId) return false\n const matches = (value: unknown): boolean => {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed === screenId || parseScreenLinkValue(trimmed) === screenId) {\n return true\n }\n // Only strings that hold a markdown link are worth parsing, which on a\n // page is almost none of them.\n if (!trimmed.includes('](')) return false\n for (const match of trimmed.matchAll(MARKDOWN_LINK_TARGET)) {\n const target = (match[1] ?? '').trim()\n if (target === screenId || parseScreenLinkValue(target) === screenId) {\n return true\n }\n }\n return false\n }\n if (Array.isArray(value)) return value.some(matches)\n if (value && typeof value === 'object') {\n return Object.values(value as Record<string, unknown>).some(matches)\n }\n return false\n }\n for (const node of Object.values(nodes)) {\n const props = (node as { props?: unknown } | undefined)?.props\n if (props && matches(props)) return true\n }\n return false\n}\n"],"names":["SCREEN_LINK_VALUE_PREFIX","COLLECTION_LINK_VALUE_PREFIX","formatCollectionLinkValue","collectionId","trim","parseCollectionLinkValue","value","undefined","trimmed","startsWith","id","slice","length","ENTRY_LINK_VALUE_PREFIX","formatEntryLinkValue","entryId","parseEntryLinkValue","parts","split","map","part","FEED_LINK_VALUE_PREFIX","formatFeedLinkValue","parseFeedLinkValue","linkTargetKind","key","formatScreenLinkValue","screenId","entry","feedCollectionId","parseScreenLinkValue","screenRoutesAnswerFor","screens","target","wanted","candidate","SAFE_HREF_PATTERN","EXTERNAL_HREF_PATTERN","bareFragmentOfLinkValue","includes","fragment","toLowerCase","bareFragmentLinkWarning","bareFragmentLinkFieldProps","_props","field","warning","input","helperText","resolveScreenHref","path","splitLinkValue","href","rawScreen","rawHref","fromScreenSlot","test","fromHrefSlot","literal","MARKDOWN_LINK_TARGET","nodesReferenceScreen","nodes","matches","match","matchAll","Array","isArray","some","Object","values","node","props"],"mappings":"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"}
@@ -123,6 +123,7 @@ export * from './expand-repeatables';
123
123
  export * from './org-roles';
124
124
  export * from './markdown-lite';
125
125
  export * from './notifications';
126
+ export * from './operator-alerts';
126
127
  export * from './definition-canvas-tree';
127
128
  export * from './ensure-canvas-root';
128
129
  export * from './repair-canvas-nodes';
@@ -34,6 +34,12 @@ export * from "./collection-entries.js";
34
34
  export * from "./collection-kind.js";
35
35
  export * from "./collection-slug.js";
36
36
  export * from "./content-authors.js";
37
+ // NOT re-exported: `./local-business` and `./site-entity-json-ld` (AGL-3383).
38
+ // This barrel is the whole namespace a realm bundle is handed and part of the
39
+ // runtime a published page loads (`check:tenant-wire-weight`); the site
40
+ // entity's JSON-LD and the LocalBusiness allow-list are read only by the
41
+ // tenant page's server render and the console's Setup → SEO card, which import
42
+ // them by path (`@aglyn/aglyn/app-utils/...`).
37
43
  export * from "./content-query-fields.js";
38
44
  export * from "./content-schema-type.js";
39
45
  export * from "./content-author-profile.js";
@@ -256,6 +262,9 @@ export * from "./expand-repeatables.js";
256
262
  export * from "./org-roles.js";
257
263
  export * from "./markdown-lite.js";
258
264
  export * from "./notifications.js";
265
+ // The operator alert registry (AGL-3377); OPERATOR_ALERT_NOTIFICATION_TYPES
266
+ // lives here now, derived from it.
267
+ export * from "./operator-alerts.js";
259
268
  export * from "./definition-canvas-tree.js";
260
269
  export * from "./ensure-canvas-root.js";
261
270
  export * from "./repair-canvas-nodes.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a node tree must satisfy before it lands on a canvas that did not\n// author it (AGL-2939): the marketplace's install sanitizer and the AI\n// plugin's node-tree validator both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\nexport * from './console-record-links'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// The booking door's shared vocabulary (AGL-2660): the query keys a booking\n// link carries, the record reference inside it, and the wording and due\n// date of what a booking files back on the record.\nexport * from './crm-booking'\n// Two contact documents folded into one (AGL-2625): the plan the console\n// previews and the server writes, reading the facet shape from `contacts`\n// and the company mirror from `crm`.\nexport * from './contact-merge'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// What the CRM's reports compute from what they read — periods, weekly\n// buckets, the lifecycle funnel, pipeline totals (AGL-2604). Pure like `crm`,\n// and beside it because it reads the stage list and the deal weighting from\n// there rather than restating either.\nexport * from './crm-reports'\n// Every CRM CSV, written once (AGL-2662): the column lists and the cells\n// each section's Export button writes, and the server's whole-collection\n// export streams — one implementation, because the console app may not\n// import the plugin whose sections used to hold it.\nexport * from './crm-csv'\n// Which holder a cross-site reader sees one contact through (AGL-2630),\n// beside the files that write it: the org-level list and the server's\n// export both flatten a contact this way.\nexport * from './contact-holder'\n// What one member is owed this morning — the day's window in a named zone,\n// the task and lead bucketing, and the words the notification and the mail\n// say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.\nexport * from './crm-digest'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm-digest`, read by the hourly route and by every\n// writer of the field.\nexport * from './crm-task-reminders'\n// Email capture (AGL-2657): the address's shape and token, which of a\n// message's addresses is the correspondent, the excerpt, and the row a\n// captured message becomes. Pure like `crm-digest`; the webhook route and\n// the address route are the readers with Firestore.\nexport * from './crm-inbound'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The next activity a record carries (AGL-2661): what `nextTaskAtMs` IS given\n// a record's open tasks, the view clause a list filters on, the report figure.\n// Pure like `crm`; the admin library's `recomputeCrmNextTaskAt` is the writer.\nexport * from './crm-next-activity'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n importNoMailServerSentence,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './list-assignment-policy'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\nexport * from './list-import'\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './email-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './campaign-attribution'\n// The edge between a campaign and the forms, screens and contacts a push is\n// coordinated across. Beside the attribution above because the two answer\n// opposite questions about the same word: attribution records where somebody\n// CAME from, this records what a merchant DECLARED belongs together.\nexport * from './campaign-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\nexport * from './form-abuse-ceiling'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\nexport * from './workflows'\nexport * from './datasets'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\nexport * from './actions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './dataset-models'\nexport * from './dataset-csv'\nexport * from './marketplace-provenance'\nexport * from './marketplace-update-state'\nexport * from './dataset-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey","importNoMailServerSentence"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\n// NOT re-exported: `./local-business` and `./site-entity-json-ld` (AGL-3383).\n// This barrel is the whole namespace a realm bundle is handed and part of the\n// runtime a published page loads (`check:tenant-wire-weight`); the site\n// entity's JSON-LD and the LocalBusiness allow-list are read only by the\n// tenant page's server render and the console's Setup → SEO card, which import\n// them by path (`@aglyn/aglyn/app-utils/...`).\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a node tree must satisfy before it lands on a canvas that did not\n// author it (AGL-2939): the marketplace's install sanitizer and the AI\n// plugin's node-tree validator both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\nexport * from './console-record-links'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// The booking door's shared vocabulary (AGL-2660): the query keys a booking\n// link carries, the record reference inside it, and the wording and due\n// date of what a booking files back on the record.\nexport * from './crm-booking'\n// Two contact documents folded into one (AGL-2625): the plan the console\n// previews and the server writes, reading the facet shape from `contacts`\n// and the company mirror from `crm`.\nexport * from './contact-merge'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// What the CRM's reports compute from what they read — periods, weekly\n// buckets, the lifecycle funnel, pipeline totals (AGL-2604). Pure like `crm`,\n// and beside it because it reads the stage list and the deal weighting from\n// there rather than restating either.\nexport * from './crm-reports'\n// Every CRM CSV, written once (AGL-2662): the column lists and the cells\n// each section's Export button writes, and the server's whole-collection\n// export streams — one implementation, because the console app may not\n// import the plugin whose sections used to hold it.\nexport * from './crm-csv'\n// Which holder a cross-site reader sees one contact through (AGL-2630),\n// beside the files that write it: the org-level list and the server's\n// export both flatten a contact this way.\nexport * from './contact-holder'\n// What one member is owed this morning — the day's window in a named zone,\n// the task and lead bucketing, and the words the notification and the mail\n// say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.\nexport * from './crm-digest'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm-digest`, read by the hourly route and by every\n// writer of the field.\nexport * from './crm-task-reminders'\n// Email capture (AGL-2657): the address's shape and token, which of a\n// message's addresses is the correspondent, the excerpt, and the row a\n// captured message becomes. Pure like `crm-digest`; the webhook route and\n// the address route are the readers with Firestore.\nexport * from './crm-inbound'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The next activity a record carries (AGL-2661): what `nextTaskAtMs` IS given\n// a record's open tasks, the view clause a list filters on, the report figure.\n// Pure like `crm`; the admin library's `recomputeCrmNextTaskAt` is the writer.\nexport * from './crm-next-activity'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n importNoMailServerSentence,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './list-assignment-policy'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\nexport * from './list-import'\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './email-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './campaign-attribution'\n// The edge between a campaign and the forms, screens and contacts a push is\n// coordinated across. Beside the attribution above because the two answer\n// opposite questions about the same word: attribution records where somebody\n// CAME from, this records what a merchant DECLARED belongs together.\nexport * from './campaign-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\nexport * from './form-abuse-ceiling'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\nexport * from './workflows'\nexport * from './datasets'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\n// The operator alert registry (AGL-3377); OPERATOR_ALERT_NOTIFICATION_TYPES\n// lives here now, derived from it.\nexport * from './operator-alerts'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\nexport * from './actions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './dataset-models'\nexport * from './dataset-csv'\nexport * from './marketplace-provenance'\nexport * from './marketplace-update-state'\nexport * from './dataset-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey","importNoMailServerSentence"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type ContentAuthorImageContext, type HostSeoEntity, type SiteEntityHost } from './content-authors';
18
+ import { type LocalBusinessType } from './local-business';
19
+ /**
20
+ * The `@type` the SITE entity publishes under — {@link contentAuthorSchemaType}
21
+ * with one refinement (AGL-3383): an entity that is not a Person and names a
22
+ * business type on the allow-list publishes as that type.
23
+ *
24
+ * Person wins over a business type, because `schema.org` gives a Person none
25
+ * of the properties a business type exists to carry. The stored `type` stays
26
+ * Organization or Person either way; every LocalBusiness subtype IS an
27
+ * Organization, so a consumer reading this as one is still right.
28
+ *
29
+ * Shared by the standalone node and the nested `publisher`, which carry the
30
+ * same `@id` and must therefore not disagree about what they are.
31
+ */
32
+ export declare function siteEntitySchemaType(entity: HostSeoEntity | null | undefined): 'Person' | 'Organization' | LocalBusinessType;
33
+ /**
34
+ * `schema.org` JSON-LD for the SITE entity (`host.seo.entity`) — the value
35
+ * `Article.publisher` and `WebSite.publisher` are built from.
36
+ *
37
+ * Lives beside {@link contentAuthorJsonLd} on purpose (AGL-2486): a site
38
+ * entity and a post author answer the same question about two different
39
+ * subjects, and the reason the Person branch was broken for two years is that
40
+ * the answer was written inline at its one call site where nothing could
41
+ * test it. One module, two entry points, one `Person`/`Organization`
42
+ * decision — {@link contentAuthorSchemaType}.
43
+ *
44
+ * `logo` is emitted verbatim on BOTH branches, unchanged from what the tenant
45
+ * has always published. It is `host.seo.entity.logo`, a plain URL field on
46
+ * the Setup form rather than a media-picker target, and re-keying it to
47
+ * `image` for a Person would change the publisher block on every page of
48
+ * every site that set one — a separate decision from this one.
49
+ */
50
+ export declare function hostSeoEntityJsonLd(entity: HostSeoEntity | null | undefined): Record<string, unknown> | undefined;
51
+ /**
52
+ * The site's publisher as a STANDALONE, fully populated `schema.org` node
53
+ * (AGL-2716).
54
+ *
55
+ * ## Why this exists next to {@link hostSeoEntityJsonLd}
56
+ *
57
+ * That function builds the value `publisher` takes: a `@type` and a `name`,
58
+ * nested inside a `WebSite` or an `Article`, gated on the author having filled
59
+ * in the Entity form. It is deliberately narrow, and every call site nests it.
60
+ *
61
+ * A consumer asking "who runs this site, and how do I reach them" — an AI
62
+ * assistant answering a contact question, a readiness audit checking whether a
63
+ * business is identifiable — looks for a TOP-LEVEL node of type `Organization`
64
+ * carrying `name`, `description`, `contactPoint` and `address`. A publisher
65
+ * nested two levels inside a `WebSite` answers a different question, and a
66
+ * publisher that is absent because nobody filled in a form answers none.
67
+ *
68
+ * So this one:
69
+ *
70
+ * - is emitted as its own `<script type="application/ld+json">`, at the top
71
+ * level, where it can be found without walking into another node;
72
+ * - FALLS BACK to the site's own identity when the Entity form is empty, so
73
+ * every site on the platform publishes a complete entity rather than only
74
+ * the sites whose author found the form;
75
+ * - carries the contact and address fields, which are what turn "a name" into
76
+ * "a business a consumer can verify".
77
+ *
78
+ * ## Why the fallback is safe
79
+ *
80
+ * It changes nothing that already exists: `hostSeoEntityJsonLd` still gates on
81
+ * `entity.name`, so `publisher` appears exactly where it appeared before. The
82
+ * fallback only decides what THIS node says, and a site's display name is its
83
+ * publisher's name on the overwhelming majority of sites — the two differ when
84
+ * a company's trading name differs from its site's, which is precisely the case
85
+ * the Entity form exists to express.
86
+ *
87
+ * ## `@id`
88
+ *
89
+ * Both this node and the nested `publisher` carry the same `@id` when an
90
+ * origin is known, so a consumer merges them into one entity instead of
91
+ * reading a site that names its publisher twice with different detail.
92
+ *
93
+ * Returns `undefined` only when there is no name to be had from anywhere —
94
+ * a host document with no entity, no display name and no SEO title.
95
+ */
96
+ export declare function siteEntityJsonLd(host: SiteEntityHost | null | undefined, context?: ContentAuthorImageContext): Record<string, unknown> | undefined;
@@ -0,0 +1,220 @@
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 { AUTHOR_NAME_MAX_LENGTH, SITE_ENTITY_FRAGMENT, contentAuthorSchemaType, hostSeoEntityImageJsonLd, siteProfileUrls } from "./content-authors.js";
18
+ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType, normalizeAreaServed, parseOpeningHours } from "./local-business.js";
19
+ /*
20
+ * The SITE entity's JSON-LD: the nested `publisher` and the standalone node.
21
+ *
22
+ * ⛔ Kept apart from `content-authors`, which the compose pipeline reads and
23
+ * therefore ships whole in the site runtime a published page downloads
24
+ * (`check:tenant-wire-weight`). Only the tenant page's server render calls
25
+ * these, and the LocalBusiness allow-list and hours parser they carry
26
+ * (AGL-3383) have no business in a visitor's bundle. Reached through
27
+ * `@aglyn/aglyn/server` only.
28
+ */ /** `content-authors`' trim-and-cap, for the fields this module reads. */ const text = (value, max = AUTHOR_NAME_MAX_LENGTH)=>typeof value === 'string' ? value.trim().slice(0, max) : '';
29
+ /**
30
+ * The `@type` the SITE entity publishes under — {@link contentAuthorSchemaType}
31
+ * with one refinement (AGL-3383): an entity that is not a Person and names a
32
+ * business type on the allow-list publishes as that type.
33
+ *
34
+ * Person wins over a business type, because `schema.org` gives a Person none
35
+ * of the properties a business type exists to carry. The stored `type` stays
36
+ * Organization or Person either way; every LocalBusiness subtype IS an
37
+ * Organization, so a consumer reading this as one is still right.
38
+ *
39
+ * Shared by the standalone node and the nested `publisher`, which carry the
40
+ * same `@id` and must therefore not disagree about what they are.
41
+ */ export function siteEntitySchemaType(entity) {
42
+ var _localBusinessType;
43
+ const base = contentAuthorSchemaType(entity == null ? void 0 : entity.type);
44
+ if (base === 'Person') return base;
45
+ return (_localBusinessType = localBusinessType(entity == null ? void 0 : entity.businessType)) != null ? _localBusinessType : base;
46
+ }
47
+ /**
48
+ * `schema.org` JSON-LD for the SITE entity (`host.seo.entity`) — the value
49
+ * `Article.publisher` and `WebSite.publisher` are built from.
50
+ *
51
+ * Lives beside {@link contentAuthorJsonLd} on purpose (AGL-2486): a site
52
+ * entity and a post author answer the same question about two different
53
+ * subjects, and the reason the Person branch was broken for two years is that
54
+ * the answer was written inline at its one call site where nothing could
55
+ * test it. One module, two entry points, one `Person`/`Organization`
56
+ * decision — {@link contentAuthorSchemaType}.
57
+ *
58
+ * `logo` is emitted verbatim on BOTH branches, unchanged from what the tenant
59
+ * has always published. It is `host.seo.entity.logo`, a plain URL field on
60
+ * the Setup form rather than a media-picker target, and re-keying it to
61
+ * `image` for a Person would change the publisher block on every page of
62
+ * every site that set one — a separate decision from this one.
63
+ */ export function hostSeoEntityJsonLd(entity) {
64
+ const name = text(entity == null ? void 0 : entity.name, 400);
65
+ if (!name) return undefined;
66
+ return {
67
+ '@type': siteEntitySchemaType(entity),
68
+ name
69
+ };
70
+ }
71
+ /**
72
+ * The site's publisher as a STANDALONE, fully populated `schema.org` node
73
+ * (AGL-2716).
74
+ *
75
+ * ## Why this exists next to {@link hostSeoEntityJsonLd}
76
+ *
77
+ * That function builds the value `publisher` takes: a `@type` and a `name`,
78
+ * nested inside a `WebSite` or an `Article`, gated on the author having filled
79
+ * in the Entity form. It is deliberately narrow, and every call site nests it.
80
+ *
81
+ * A consumer asking "who runs this site, and how do I reach them" — an AI
82
+ * assistant answering a contact question, a readiness audit checking whether a
83
+ * business is identifiable — looks for a TOP-LEVEL node of type `Organization`
84
+ * carrying `name`, `description`, `contactPoint` and `address`. A publisher
85
+ * nested two levels inside a `WebSite` answers a different question, and a
86
+ * publisher that is absent because nobody filled in a form answers none.
87
+ *
88
+ * So this one:
89
+ *
90
+ * - is emitted as its own `<script type="application/ld+json">`, at the top
91
+ * level, where it can be found without walking into another node;
92
+ * - FALLS BACK to the site's own identity when the Entity form is empty, so
93
+ * every site on the platform publishes a complete entity rather than only
94
+ * the sites whose author found the form;
95
+ * - carries the contact and address fields, which are what turn "a name" into
96
+ * "a business a consumer can verify".
97
+ *
98
+ * ## Why the fallback is safe
99
+ *
100
+ * It changes nothing that already exists: `hostSeoEntityJsonLd` still gates on
101
+ * `entity.name`, so `publisher` appears exactly where it appeared before. The
102
+ * fallback only decides what THIS node says, and a site's display name is its
103
+ * publisher's name on the overwhelming majority of sites — the two differ when
104
+ * a company's trading name differs from its site's, which is precisely the case
105
+ * the Entity form exists to express.
106
+ *
107
+ * ## `@id`
108
+ *
109
+ * Both this node and the nested `publisher` carry the same `@id` when an
110
+ * origin is known, so a consumer merges them into one entity instead of
111
+ * reading a site that names its publisher twice with different detail.
112
+ *
113
+ * Returns `undefined` only when there is no name to be had from anywhere —
114
+ * a host document with no entity, no display name and no SEO title.
115
+ */ export function siteEntityJsonLd(host, context) {
116
+ var _ref, _ref1;
117
+ var _host_seo, _host_seo1, _context_origin, _host_seo2, _entity_address, _entity_address1, _entity_address2, _entity_address3, _entity_address4;
118
+ const entity = (_ref = host == null ? void 0 : (_host_seo = host.seo) == null ? void 0 : _host_seo.entity) != null ? _ref : undefined;
119
+ const name = text(entity == null ? void 0 : entity.name, 400) || text(host == null ? void 0 : host.displayName, 400) || text(host == null ? void 0 : (_host_seo1 = host.seo) == null ? void 0 : _host_seo1.title, 400);
120
+ if (!name) return undefined;
121
+ const origin = (_ref1 = context == null ? void 0 : (_context_origin = context.origin) == null ? void 0 : _context_origin.replace(/\/+$/, '')) != null ? _ref1 : '';
122
+ const description = text(entity == null ? void 0 : entity.description, 800) || text(host == null ? void 0 : (_host_seo2 = host.seo) == null ? void 0 : _host_seo2.description, 800);
123
+ const url = text(entity == null ? void 0 : entity.url, 800) || origin;
124
+ /*
125
+ `sameAs` is de-duplicated and https-only, matching the author serializer:
126
+ a profile URL that is not fetchable teaches a consumer nothing, and the
127
+ same profile listed twice reads as two identities.
128
+
129
+ Through `siteProfileUrls` (AGL-3148) so the site's rendered social links
130
+ count as profiles it claims, rather than only the SEO field that no host
131
+ on the platform had filled in.
132
+ */ const sameAs = siteProfileUrls(host);
133
+ /*
134
+ A contactPoint needs something to contact. `contactType` alone is a label
135
+ on nothing, so the point is emitted only when an address or a number is
136
+ present — `schema.org` requires neither, and a consumer that finds a
137
+ ContactPoint with no channel has been told less than it would learn from
138
+ its absence.
139
+ */ const email = text(entity == null ? void 0 : entity.email, 320);
140
+ const telephone = text(entity == null ? void 0 : entity.telephone, 64);
141
+ const contactType = text(entity == null ? void 0 : entity.contactType, 120) || 'customer support';
142
+ const contactPoint = email || telephone ? _extends({
143
+ '@type': 'ContactPoint',
144
+ contactType
145
+ }, email ? {
146
+ email
147
+ } : {}, telephone ? {
148
+ telephone
149
+ } : {}) : undefined;
150
+ const addressFields = {
151
+ streetAddress: text(entity == null ? void 0 : (_entity_address = entity.address) == null ? void 0 : _entity_address.streetAddress, 200),
152
+ addressLocality: text(entity == null ? void 0 : (_entity_address1 = entity.address) == null ? void 0 : _entity_address1.addressLocality, 120),
153
+ addressRegion: text(entity == null ? void 0 : (_entity_address2 = entity.address) == null ? void 0 : _entity_address2.addressRegion, 120),
154
+ postalCode: text(entity == null ? void 0 : (_entity_address3 = entity.address) == null ? void 0 : _entity_address3.postalCode, 40),
155
+ addressCountry: text(entity == null ? void 0 : (_entity_address4 = entity.address) == null ? void 0 : _entity_address4.addressCountry, 120)
156
+ };
157
+ const address = Object.entries(addressFields).filter(([, value])=>value);
158
+ const schemaType = siteEntitySchemaType(entity);
159
+ const business = schemaType !== 'Person' && schemaType !== 'Organization' ? localBusinessJsonLd(entity, telephone) : {};
160
+ return _extends({
161
+ '@context': 'https://schema.org',
162
+ '@type': schemaType
163
+ }, origin ? {
164
+ '@id': `${origin}/${SITE_ENTITY_FRAGMENT}`
165
+ } : {}, {
166
+ name
167
+ }, description ? {
168
+ description
169
+ } : {}, url ? {
170
+ url
171
+ } : {}, hostSeoEntityImageJsonLd(entity, context), sameAs.length ? {
172
+ sameAs
173
+ } : {}, contactPoint ? {
174
+ contactPoint
175
+ } : {}, address.length ? {
176
+ address: _extends({
177
+ '@type': 'PostalAddress'
178
+ }, Object.fromEntries(address))
179
+ } : {}, business);
180
+ }
181
+ /**
182
+ * The properties only a LocalBusiness carries (AGL-3383), spread after
183
+ * everything an Organization publishes so an Organization's output is
184
+ * unchanged key for key.
185
+ *
186
+ * - `telephone` on the node itself as well as on its `contactPoint`: a
187
+ * LocalBusiness has the property directly, and it is where a local result
188
+ * reads the number from.
189
+ * - `areaServed` as named `City` places. A service-area business commonly has
190
+ * no street address to publish, and this is what says where it works.
191
+ * - `openingHoursSpecification` from the hours text, invalid lines dropped.
192
+ *
193
+ * Every one of them is omitted when empty, never published as `[]` or `''`.
194
+ */ function localBusinessJsonLd(entity, telephone) {
195
+ const areaServed = normalizeAreaServed(entity == null ? void 0 : entity.areaServed);
196
+ const hours = parseOpeningHours(entity == null ? void 0 : entity.openingHours);
197
+ const priceRange = text(entity == null ? void 0 : entity.priceRange, PRICE_RANGE_MAX_LENGTH);
198
+ const paymentAccepted = text(entity == null ? void 0 : entity.paymentAccepted, PAYMENT_ACCEPTED_MAX_LENGTH);
199
+ return _extends({}, telephone ? {
200
+ telephone
201
+ } : {}, areaServed.length ? {
202
+ areaServed: areaServed.map((name)=>({
203
+ '@type': 'City',
204
+ name
205
+ }))
206
+ } : {}, hours.length ? {
207
+ openingHoursSpecification: hours.map((row)=>({
208
+ '@type': 'OpeningHoursSpecification',
209
+ dayOfWeek: row.days,
210
+ opens: row.opens,
211
+ closes: row.closes
212
+ }))
213
+ } : {}, priceRange ? {
214
+ priceRange
215
+ } : {}, paymentAccepted ? {
216
+ paymentAccepted
217
+ } : {});
218
+ }
219
+
220
+ //# sourceMappingURL=site-entity-json-ld.js.map