@aglyn/aglyn 1.0.0-beta.215 → 1.0.0-beta.217

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 (33) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/deferred-image.d.ts +3 -3
  3. package/src/lib/app-utils/deferred-image.js +3 -3
  4. package/src/lib/app-utils/deferred-image.js.map +1 -1
  5. package/src/lib/app-utils/docs-help.generated.d.ts +2 -2
  6. package/src/lib/app-utils/docs-help.generated.js +2 -1
  7. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  8. package/src/lib/app-utils/docs-index.generated.js +35 -21
  9. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  10. package/src/lib/app-utils/marketplace-theme.d.ts +9 -4
  11. package/src/lib/app-utils/marketplace-theme.js +11 -1
  12. package/src/lib/app-utils/marketplace-theme.js.map +1 -1
  13. package/src/lib/app-utils/publisher-agreement.d.ts +36 -1
  14. package/src/lib/app-utils/publisher-agreement.js +48 -1
  15. package/src/lib/app-utils/publisher-agreement.js.map +1 -1
  16. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  17. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  18. package/src/lib/app-utils/theme-library.d.ts +206 -0
  19. package/src/lib/app-utils/theme-library.js +566 -0
  20. package/src/lib/app-utils/theme-library.js.map +1 -0
  21. package/src/lib/foundation/definitions/platform.types.js +2 -1
  22. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  23. package/src/lib/plugin-manager/feature-plugins.d.ts +44 -0
  24. package/src/lib/plugin-manager/feature-plugins.js +25 -0
  25. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  26. package/src/lib/plugin-manager/first-party-plugins.generated.js +11 -0
  27. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  28. package/src/lib/plugin-manager/plugin-services.js +20 -2
  29. package/src/lib/plugin-manager/plugin-services.js.map +1 -1
  30. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  31. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  32. package/src/lib/plugin-manager/record-captured-contact.js +12 -3
  33. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
@@ -133,6 +133,8 @@ export interface ThemeHostDocument {
133
133
  sha256?: string | null;
134
134
  listingId?: string;
135
135
  } | null;
136
+ /** The library theme the site picked (AGL-3404) — see `theme-library`. */
137
+ themeSelection?: unknown;
136
138
  }
137
139
  /** Reads the stored theme override off a host document, tolerating junk. */
138
140
  export declare function readThemeOverride(host: ThemeHostDocument | null | undefined): ArtifactOverride | undefined;
@@ -161,16 +163,19 @@ export declare function resolveSiteTheme(host: ThemeHostDocument | null | undefi
161
163
  * - `installed` — a marketplace theme. The site's edits are its override
162
164
  * patch over the publisher's version, which stays untouched and can still
163
165
  * take an update.
164
- * - `custom` — the site's own theme, edited in place.
165
- * - `default` — the platform default. The site stores nothing until it
166
- * changes something, and then only what it changed.
166
+ * - `preset` — a built-in theme a plugin contributes (AGL-3404).
167
+ * - `custom` — one of the site's own themes.
168
+ * - `default` — the platform default.
169
+ *
170
+ * Whichever it is, an edit is an override patch over the picked theme and the
171
+ * theme itself is never rewritten (AGL-3404), so every source can be restored.
167
172
  *
168
173
  * One reading for every surface that needs it — the page that lays out the
169
174
  * Theme section, a widget proposing a change there, and a generator deciding
170
175
  * what its change will be stored as — so none of them can file a site under
171
176
  * a different source than the save path does.
172
177
  */
173
- export type HostThemeSource = 'installed' | 'custom' | 'default';
178
+ export type HostThemeSource = 'installed' | 'preset' | 'custom' | 'default';
174
179
  export declare function hostThemeSource(host: ThemeHostDocument | null | undefined): HostThemeSource;
175
180
  /**
176
181
  * Was this override authored against the theme currently installed?
@@ -397,8 +397,18 @@ function validateFonts(theme, warnings) {
397
397
  if (!override) return base;
398
398
  return resolveOverride(base != null ? base : {}, override.patch);
399
399
  }
400
+ const THEME_SOURCES = [
401
+ 'installed',
402
+ 'preset',
403
+ 'custom',
404
+ 'default'
405
+ ];
400
406
  export function hostThemeSource(host) {
401
- var _host_themeInstalledFrom;
407
+ var _host_themeSelection, _host_themeInstalledFrom;
408
+ const picked = host == null ? void 0 : (_host_themeSelection = host.themeSelection) == null ? void 0 : _host_themeSelection.kind;
409
+ if (THEME_SOURCES.includes(picked)) {
410
+ return picked;
411
+ }
402
412
  if (host == null ? void 0 : (_host_themeInstalledFrom = host.themeInstalledFrom) == null ? void 0 : _host_themeInstalledFrom.listingId) return 'installed';
403
413
  return (host == null ? void 0 : host.theme) && Object.keys(host.theme).length ? 'custom' : 'default';
404
414
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/marketplace-theme.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n HostTheme,\n HostThemeFont,\n HostThemeScheme,\n HostThemeSchemeColors,\n} from '@aglyn/shared-data-types'\nimport {\n diffOverride,\n isEmptyOverride,\n overrideConflicts,\n overridePaths,\n readArtifactOverride,\n resolveOverride,\n type ArtifactOverride,\n} from './marketplace-overrides'\n\n/**\n * Publish-time validation for themes as a marketplace artifact (AGL-1020).\n *\n * A plugin bundle is validated because it is code and the risk is a\n * compromised site. A theme is data, so the risk is different in kind but not\n * in consequence: the failure mode is an unreadable or half-painted site, which\n * a publisher cannot see from their own machine because their fonts are\n * installed and their eyes already know what the text says.\n *\n * Both of the things that go wrong are mechanically checkable, so they are\n * checked here rather than left to review:\n *\n * * **Completeness.** Light and dark are not one palette with a filter. A theme\n * that defines only light is half a theme, and installing it leaves every\n * dark-mode visitor on MUI's stock blue — which reads as the site being\n * broken, not as the theme being partial.\n * * **Contrast.** Text on its background, and label on its button, are the two\n * pairs that make a site unusable when they are wrong. WCAG gives an exact\n * ratio, so \"is this readable\" has an answer rather than an opinion.\n *\n * Errors refuse the publish; warnings are shown and can be published past. The\n * split is deliberate — a 3.9:1 heading is a judgement call a designer may\n * legitimately win, but a theme with no dark scheme is not a complete artifact\n * whoever is asking.\n *\n * Pure and MUI-free so the publish route, the console's pre-flight and the\n * tests all reach the same verdict from the same code.\n */\n\nexport interface ThemeValidationIssue {\n /** Dotted path into the theme, so the editor can focus the offending field. */\n path: string\n message: string\n}\n\nexport interface ThemeValidation {\n /** No errors. Warnings do not block. */\n ok: boolean\n errors: ThemeValidationIssue[]\n warnings: ThemeValidationIssue[]\n}\n\n/**\n * WCAG AA for body text. The same 4.5 the accessibility audit uses, rather\n * than a house number, so a theme that passes here passes there.\n */\nexport const CONTRAST_AA = 4.5\n\n/**\n * WCAG AA for large text (18pt+/14pt bold) and UI component boundaries. Button\n * labels are judged against this rather than 4.5 because they are large, short\n * and centred, and holding them to body-text contrast rejects palettes that are\n * genuinely fine.\n */\nexport const CONTRAST_AA_LARGE = 3\n\nconst SCHEMES: HostThemeScheme[] = ['light', 'dark']\n\n/**\n * Font families that need no loading. Anything else is either declared in the\n * theme's own `fonts` array or will silently fall back to the browser's default\n * — which is the failure the issue calls out, so it is at least a warning.\n */\nconst SYSTEM_FAMILIES = new Set(\n [\n 'system-ui',\n 'sans-serif',\n 'serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n '-apple-system',\n 'blinkmacsystemfont',\n 'segoe ui',\n 'roboto',\n 'helvetica',\n 'helvetica neue',\n 'arial',\n 'georgia',\n 'times new roman',\n 'courier new',\n 'inherit',\n 'initial',\n ].map((family) => family.toLowerCase()),\n)\n\n/**\n * Parses a CSS colour into sRGB channels, or null when it is not something we\n * can reason about.\n *\n * `null` means \"not checked\", never \"failed\": a theme is free to use `color-mix`\n * or a CSS variable, and refusing to publish because this parser is narrower\n * than CSS would be a validator inventing a rule the platform does not have.\n */\nexport function parseColor(\n value: unknown,\n): { r: number; g: number; b: number } | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n\n const hex = /^#([0-9a-f]{3,8})$/.exec(text)\n if (hex) {\n const digits = hex[1]\n if (digits.length === 3 || digits.length === 4) {\n return {\n r: parseInt(digits[0] + digits[0], 16),\n g: parseInt(digits[1] + digits[1], 16),\n b: parseInt(digits[2] + digits[2], 16),\n }\n }\n if (digits.length === 6 || digits.length === 8) {\n return {\n r: parseInt(digits.slice(0, 2), 16),\n g: parseInt(digits.slice(2, 4), 16),\n b: parseInt(digits.slice(4, 6), 16),\n }\n }\n return null\n }\n\n const rgb = /^rgba?\\(([^)]+)\\)$/.exec(text)\n if (rgb) {\n const parts = rgb[1].split(/[,/\\s]+/).filter(Boolean)\n if (parts.length < 3) return null\n const channels = parts.slice(0, 3).map((part) =>\n part.endsWith('%')\n ? (Number(part.slice(0, -1)) / 100) * 255\n : Number(part),\n )\n if (channels.some((channel) => !Number.isFinite(channel))) return null\n const [r, g, b] = channels\n return { r, g, b }\n }\n\n if (text === 'white') return { r: 255, g: 255, b: 255 }\n if (text === 'black') return { r: 0, g: 0, b: 0 }\n return null\n}\n\n/** WCAG relative luminance. */\nfunction luminance(channels: { r: number; g: number; b: number }): number {\n const channel = (raw: number) => {\n const value = Math.min(255, Math.max(0, raw)) / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n }\n return (\n 0.2126 * channel(channels.r) +\n 0.7152 * channel(channels.g) +\n 0.0722 * channel(channels.b)\n )\n}\n\n/**\n * WCAG contrast ratio between two colours, or null when either is unparseable.\n * Ranges 1 (identical) to 21 (black on white).\n */\nexport function contrastRatio(a: unknown, b: unknown): number | null {\n const first = parseColor(a)\n const second = parseColor(b)\n if (!first || !second) return null\n const light = Math.max(luminance(first), luminance(second))\n const dark = Math.min(luminance(first), luminance(second))\n return (light + 0.05) / (dark + 0.05)\n}\n\nconst round = (ratio: number) => Math.round(ratio * 10) / 10\n\n/**\n * The colours a scheme must actually define to be usable on its own.\n *\n * Deliberately short. MUI derives an enormous amount from very little, and\n * demanding a full palette would reject good themes; but a scheme that names no\n * primary, no background and no text has not expressed an identity at all — it\n * is an empty object that happens to have a key.\n */\nfunction validateScheme(\n scheme: HostThemeScheme,\n colors: HostThemeSchemeColors | undefined,\n errors: ThemeValidationIssue[],\n warnings: ThemeValidationIssue[],\n): void {\n const at = `colorSchemes.${scheme}`\n if (!colors || !Object.keys(colors).length) {\n errors.push({\n path: at,\n message:\n `This theme defines no ${scheme} scheme. Light and dark are not one ` +\n 'palette with a filter — install it and every visitor in ' +\n `${scheme} mode sees the stock palette instead of your design.`,\n })\n return\n }\n if (!colors.primary?.main) {\n errors.push({\n path: `${at}.primary.main`,\n message: `The ${scheme} scheme has no primary color.`,\n })\n }\n\n const background = colors.background?.default\n const paper = colors.background?.paper\n const text = colors.text?.primary\n const secondaryText = colors.text?.secondary\n\n // Text on background is the pair that decides whether a site can be read at\n // all, so it is the one contrast failure that refuses the publish.\n const bodyContrast = contrastRatio(text, background)\n if (bodyContrast != null && bodyContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.text.primary`,\n message:\n `Body text on the ${scheme} background is ${round(bodyContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum for readable text.`,\n })\n }\n\n const paperContrast = contrastRatio(text, paper)\n if (paperContrast != null && paperContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.background.paper`,\n message:\n `Body text on ${scheme} cards and menus is ${round(paperContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum. Surfaces inherit this everywhere ` +\n 'a Paper is used, which is most of the site.',\n })\n }\n\n // Secondary text is legitimately dimmer by design, so it warns rather than\n // refuses — but silently shipping unreadable captions is worse than saying so.\n const captionContrast = contrastRatio(secondaryText, background)\n if (captionContrast != null && captionContrast < CONTRAST_AA) {\n warnings.push({\n path: `${at}.text.secondary`,\n message:\n `Secondary text on the ${scheme} background is ` +\n `${round(captionContrast)}:1. Captions and helper text will be hard ` +\n 'to read.',\n })\n }\n\n // A button whose label disappears into its own fill. Only checkable when the\n // theme states contrastText — otherwise MUI derives one, and derives it well.\n for (const key of ['primary', 'secondary', 'error'] as const) {\n const color = colors[key]\n const ratio = contrastRatio(color?.contrastText, color?.main)\n if (ratio != null && ratio < CONTRAST_AA_LARGE) {\n warnings.push({\n path: `${at}.${key}.contrastText`,\n message:\n `The ${scheme} ${key} button label is ${round(ratio)}:1 against its ` +\n `own fill, below ${CONTRAST_AA_LARGE}:1. Leave contrast text unset ` +\n 'to let the theme derive a readable one.',\n })\n }\n }\n}\n\n/**\n * Every family named in the typography, split out of its CSS stack.\n *\n * A stack is a fallback chain, so only the FIRST entry is the one the design\n * depends on; the rest exist precisely to be substituted and warning about them\n * would be noise.\n */\nfunction declaredFamilies(theme: HostTheme): string[] {\n const stacks: string[] = []\n if (theme.typography?.fontFamily) stacks.push(theme.typography.fontFamily)\n for (const variant of Object.values(theme.typography?.variants ?? {})) {\n if (variant?.fontFamily) stacks.push(variant.fontFamily)\n }\n const primary: string[] = []\n for (const stack of stacks) {\n const first = stack.split(',')[0]?.trim().replace(/^[\"']|[\"']$/g, '')\n if (first) primary.push(first)\n }\n return primary\n}\n\nfunction validateFonts(\n theme: HostTheme,\n warnings: ThemeValidationIssue[],\n): void {\n const loadable = new Set(\n (theme.fonts ?? [])\n .map((font: HostThemeFont) => font.family?.trim().toLowerCase())\n .filter(Boolean) as string[],\n )\n const seen = new Set<string>()\n for (const family of declaredFamilies(theme)) {\n const key = family.toLowerCase()\n if (seen.has(key) || loadable.has(key) || SYSTEM_FAMILIES.has(key)) continue\n seen.add(key)\n warnings.push({\n path: 'typography.fontFamily',\n message:\n `\"${family}\" is used but not listed in the theme's fonts, so it will ` +\n 'only appear on machines that happen to have it installed. Add it as ' +\n 'a Google font, or fall back to a system family.',\n })\n }\n}\n\n/**\n * Validates a theme for publishing.\n *\n * The same function backs the console's pre-flight and the publish route: a\n * publisher who is told \"this will be refused\" and then has it accepted (or the\n * reverse) stops trusting either, so there is one verdict from one place.\n */\nexport function validateThemeForPublish(\n theme: HostTheme | null | undefined,\n): ThemeValidation {\n const errors: ThemeValidationIssue[] = []\n const warnings: ThemeValidationIssue[] = []\n\n if (!theme || !Object.keys(theme).length) {\n return {\n ok: false,\n errors: [\n {\n path: '',\n message:\n 'This site has no theme customisations yet, so there is nothing ' +\n 'to publish.',\n },\n ],\n warnings,\n }\n }\n\n for (const scheme of SCHEMES) {\n validateScheme(scheme, theme.colorSchemes?.[scheme], errors, warnings)\n }\n validateFonts(theme, warnings)\n\n return { ok: !errors.length, errors, warnings }\n}\n\n/**\n * Every field of a `HostTheme` that is part of the published artifact.\n *\n * Typed as a total record over `keyof HostTheme` on purpose: adding a field to\n * `HostTheme` fails to compile here until someone decides whether it ships. The\n * alternative — a hand-written list of `if` statements — silently drops the new\n * field, and a theme that loses one is not obviously broken, it is subtly\n * wrong. `mixins` arrived exactly that way (AGL-1242, toolbar heights) while\n * this was being written, which is why the check is a type and not a comment.\n *\n * A `false` means \"host-local, never published\". `darkScheme` is the one such\n * field: whether visitors get a dark scheme is a decision about a SITE's\n * content (does it read in dark?), not about the design being published, so a\n * publisher's opt-out must not travel with the theme and an installer's must\n * not read as a divergence from it.\n */\nconst THEME_ARTIFACT_FIELDS: Record<keyof Required<HostTheme>, boolean> = {\n colorSchemes: true,\n typography: true,\n fonts: true,\n shape: true,\n spacing: true,\n mixins: true,\n components: true,\n darkScheme: false,\n}\n\n/**\n * The theme content as published — the artifact and nothing else.\n *\n * What goes in here is what \"reset to the publisher's version\" restores and\n * what the update diff compares against, so it must hold no per-install data:\n * a host document also carries `updatedAt`, `updatedBy` and whatever else the\n * editor writes alongside, and any of those in the content would read as a user\n * edit on the next diff.\n *\n * Empty branches are dropped so two themes that render identically also hash\n * identically — `{fonts: []}` and `{}` must not produce two base snapshots.\n */\nexport function themeArtifactContent(\n theme: HostTheme | null | undefined,\n): HostTheme {\n const content: Record<string, unknown> = {}\n if (!theme) return content\n for (const [key, ships] of Object.entries(THEME_ARTIFACT_FIELDS)) {\n if (!ships) continue\n const value = (theme as Record<string, unknown>)[key]\n if (value == null) continue\n if (Array.isArray(value) ? !value.length : typeof value === 'object'\n ? !Object.keys(value as object).length\n : false) {\n continue\n }\n content[key] = value\n }\n return content\n}\n\n/**\n * A short, human summary of what a theme actually contains, for the install\n * confirmation and the listing card.\n *\n * Installing a theme replaces a site's visual identity, so the confirmation has\n * to say what is about to change in terms someone recognises — \"both schemes,\n * 2 fonts, 4 component styles\" — rather than showing them a JSON blob and\n * asking them to agree to it.\n */\nexport function describeTheme(theme: HostTheme | null | undefined): string[] {\n if (!theme) return []\n const parts: string[] = []\n const schemes = SCHEMES.filter(\n (scheme) => Object.keys(theme.colorSchemes?.[scheme] ?? {}).length,\n )\n if (schemes.length === 2) parts.push('Light and dark schemes')\n else if (schemes.length === 1) parts.push(`${schemes[0]} scheme only`)\n\n const fonts = theme.fonts?.length ?? 0\n if (fonts) parts.push(`${fonts} font${fonts === 1 ? '' : 's'}`)\n if (theme.typography?.variants) {\n const count = Object.keys(theme.typography.variants).length\n if (count) parts.push(`${count} text style${count === 1 ? '' : 's'}`)\n }\n if (theme.shape?.borderRadius != null) {\n parts.push(`${theme.shape.borderRadius}px corners`)\n }\n if (typeof theme.spacing === 'number') parts.push(`${theme.spacing}px spacing`)\n const components = Object.keys(theme.components ?? {}).length\n if (components) {\n parts.push(`${components} component style${components === 1 ? '' : 's'}`)\n }\n return parts\n}\n\n/* ------------------------------------------------------------------------ *\n * The override layer, applied to themes (AGL-1021)\n * ------------------------------------------------------------------------ */\n\n/**\n * The field on a host document holding the site's theme override.\n *\n * A theme is a FIELD on the host, not a document of its own, so it cannot use\n * `ARTIFACT_OVERRIDE_FIELD` — a host may one day override more than one thing,\n * and a bare `overrides` would be the first to claim the name. The override\n * layer is general precisely so a namespaced field works with it unchanged.\n */\nexport const THEME_OVERRIDE_FIELD = 'themeOverride'\n\n/** A host document, as far as the theme layer is concerned. */\nexport interface ThemeHostDocument {\n theme?: HostTheme | null\n themeOverride?: unknown\n themeInstalledFrom?: { sha256?: string | null; listingId?: string } | null\n}\n\n/** Reads the stored theme override off a host document, tolerating junk. */\nexport function readThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ArtifactOverride | undefined {\n // The override layer's reader is keyed to its own field name, so hand it a\n // shim rather than duplicating its validation — which is where the \"a\n // top-level null blanks the artifact\" guard lives.\n return readArtifactOverride({ overrides: host?.themeOverride })\n}\n\n/**\n * The site's effective theme: the marketplace theme (or the site's own) with\n * this site's overrides resolved over it.\n *\n * This is the MIDDLE and TOP of the three layers the issue describes. The\n * bottom — the platform default — is applied further down by\n * `HostThemeProvider`, which layers whatever this returns onto the brand base\n * (AGL-1180) so a theme that omits a value still yields a complete theme\n * rather than an undefined-shaped hole. Doing it there rather than here is not\n * a detail: the default is a runtime MUI theme with FUNCTION style overrides\n * that JSON cannot represent, so a \"default\" merged at this layer would be a\n * lossy copy of the real one.\n *\n * Every surface that renders a site's appearance calls this — the tenant, all\n * four besigner editors, and the document preview — because a theme that\n * resolves differently in the editor than on the site is worse than one that\n * does not resolve at all.\n */\nexport function resolveSiteTheme(\n host: ThemeHostDocument | null | undefined,\n): HostTheme | undefined {\n const base = host?.theme ?? undefined\n const override = readThemeOverride(host)\n if (!override) return base\n return resolveOverride<HostTheme>(base ?? {}, override.patch)\n}\n\n/**\n * Where a site's theme came from (AGL-2938).\n *\n * - `installed` — a marketplace theme. The site's edits are its override\n * patch over the publisher's version, which stays untouched and can still\n * take an update.\n * - `custom` — the site's own theme, edited in place.\n * - `default` — the platform default. The site stores nothing until it\n * changes something, and then only what it changed.\n *\n * One reading for every surface that needs it — the page that lays out the\n * Theme section, a widget proposing a change there, and a generator deciding\n * what its change will be stored as — so none of them can file a site under\n * a different source than the save path does.\n */\nexport type HostThemeSource = 'installed' | 'custom' | 'default'\n\nexport function hostThemeSource(\n host: ThemeHostDocument | null | undefined,\n): HostThemeSource {\n if (host?.themeInstalledFrom?.listingId) return 'installed'\n return host?.theme && Object.keys(host.theme).length ? 'custom' : 'default'\n}\n\n/**\n * Was this override authored against the theme currently installed?\n *\n * False after a theme swap, which is the whole reason `baseSha256` is recorded.\n * Overrides deliberately SURVIVE a swap (the issue's call, and the right one —\n * a brand colour still expresses intent when the theme underneath it changes)\n * but surviving silently is what would surprise people, so every surface that\n * shows an override can ask this and say so.\n *\n * True when there is no override, and when either side has no hash to compare:\n * \"we cannot tell\" must not render as \"these are stale\".\n */\nexport function isOverrideForCurrentTheme(\n host: ThemeHostDocument | null | undefined,\n): boolean {\n const override = readThemeOverride(host)\n if (!override?.baseSha256) return true\n const installed = host?.themeInstalledFrom?.sha256\n if (!installed) return true\n return override.baseSha256 === installed\n}\n\n/** One row of the \"what have I changed?\" view. */\nexport interface ThemeOverrideEntry {\n /** Dotted path into the theme, e.g. `colorSchemes.dark.primary.main`. */\n path: string\n /** A human label for the path, for a UI that is not a JSON viewer. */\n label: string\n /** Which scheme this touches, when it touches one. */\n scheme?: HostThemeScheme\n /** The publisher's value, or undefined when the override adds the field. */\n themeValue: unknown\n /** What this site set it to, or undefined when the override removes it. */\n overrideValue: unknown\n}\n\nconst SEGMENT_LABELS: Record<string, string> = {\n darkScheme: 'Dark scheme',\n colorSchemes: 'Color',\n typography: 'Typography',\n components: 'Component',\n shape: 'Shape',\n spacing: 'Spacing',\n fonts: 'Fonts',\n mixins: 'Layout',\n variants: '',\n background: 'background',\n text: 'text',\n main: '',\n contrastText: 'label color',\n borderRadius: 'corner radius',\n fontFamily: 'font',\n}\n\n/**\n * A readable label for a theme path.\n *\n * \"Colour · dark · primary\" beats `colorSchemes.dark.primary.main`, and the\n * difference matters because this list is the answer to \"what have I changed?\"\n * — a question nobody asks wanting to read JSON paths.\n */\nexport function describeThemePath(path: string): string {\n const segments = path.split('.').filter(Boolean)\n const words: string[] = []\n for (const segment of segments) {\n const mapped = SEGMENT_LABELS[segment]\n if (mapped === '') continue\n words.push(mapped ?? segment)\n }\n return words.join(' · ') || 'the whole theme'\n}\n\n/**\n * \"What have I changed?\" — the patch, rendered.\n *\n * Literally a read of the stored override rather than a diff computed at call\n * time, which is the property the whole layer exists to provide: the answer is\n * the thing that was saved, so it cannot disagree with what will be applied.\n */\nexport function describeThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overridePaths(override.patch).map((path) => {\n const segments = path.split('.')\n const scheme =\n segments[0] === 'colorSchemes' &&\n (segments[1] === 'light' || segments[1] === 'dark')\n ? (segments[1] as HostThemeScheme)\n : undefined\n return {\n path,\n label: describeThemePath(path),\n scheme,\n themeValue: readThemePath(base, path),\n overrideValue: readThemePath(resolved, path),\n }\n })\n}\n\n/** Reads a dotted path out of a theme. */\nfunction readThemePath(theme: unknown, path: string): unknown {\n let cursor: unknown = theme\n for (const segment of path.split('.')) {\n if (cursor == null || typeof cursor !== 'object') return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * The override this site should store after the theme editor produced `edited`.\n *\n * The editor edits the RESOLVED theme — that is what it renders and what the\n * user sees — so the patch is the difference between the publisher's base and\n * what came back. Diffing here rather than tracking per-field dirty state means\n * a field the user set and then set back to the theme's value stops being an\n * override, which is what \"reset\" means without needing a reset button to have\n * been pressed.\n */\nexport function themeOverridePatch(\n host: ThemeHostDocument | null | undefined,\n edited: HostTheme,\n): unknown {\n return diffOverride(host?.theme ?? {}, edited)\n}\n\n/**\n * Where this site's overrides sit on top of values a theme update also changes\n * — the only place a conflict can exist.\n *\n * Narrow by construction: an override touching four paths can conflict in at\n * most four places however much of the theme the publisher rewrote. That is the\n * difference between \"the publisher also changed your heading colour, whose\n * wins?\" and \"this update rewrites your theme, continue?\".\n */\nexport function themeUpdateConflicts(\n host: ThemeHostDocument | null | undefined,\n incoming: HostTheme,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override || isEmptyOverride(override.patch)) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overrideConflicts(base, override.patch, incoming).map((path) => ({\n path,\n label: describeThemePath(path),\n scheme:\n path.startsWith('colorSchemes.light.')\n ? ('light' as const)\n : path.startsWith('colorSchemes.dark.')\n ? ('dark' as const)\n : undefined,\n themeValue: readThemePath(incoming, path),\n overrideValue: readThemePath(resolved, path),\n }))\n}\n"],"names":["diffOverride","isEmptyOverride","overrideConflicts","overridePaths","readArtifactOverride","resolveOverride","CONTRAST_AA","CONTRAST_AA_LARGE","SCHEMES","SYSTEM_FAMILIES","Set","map","family","toLowerCase","parseColor","value","text","trim","hex","exec","digits","length","r","parseInt","g","b","slice","rgb","parts","split","filter","Boolean","channels","part","endsWith","Number","some","channel","isFinite","luminance","raw","Math","min","max","contrastRatio","a","first","second","light","dark","round","ratio","validateScheme","scheme","colors","errors","warnings","at","Object","keys","push","path","message","primary","main","background","default","paper","secondaryText","secondary","bodyContrast","paperContrast","captionContrast","key","color","contrastText","declaredFamilies","theme","stacks","typography","fontFamily","variant","values","variants","stack","replace","validateFonts","loadable","fonts","font","seen","has","add","validateThemeForPublish","ok","colorSchemes","THEME_ARTIFACT_FIELDS","shape","spacing","mixins","components","darkScheme","themeArtifactContent","content","ships","entries","Array","isArray","describeTheme","schemes","count","borderRadius","THEME_OVERRIDE_FIELD","readThemeOverride","host","overrides","themeOverride","resolveSiteTheme","base","undefined","override","patch","hostThemeSource","themeInstalledFrom","listingId","isOverrideForCurrentTheme","baseSha256","installed","sha256","SEGMENT_LABELS","describeThemePath","segments","words","segment","mapped","join","describeThemeOverride","resolved","label","themeValue","readThemePath","overrideValue","cursor","themeOverridePatch","edited","themeUpdateConflicts","incoming","startsWith"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/marketplace-theme.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n HostTheme,\n HostThemeFont,\n HostThemeScheme,\n HostThemeSchemeColors,\n} from '@aglyn/shared-data-types'\nimport {\n diffOverride,\n isEmptyOverride,\n overrideConflicts,\n overridePaths,\n readArtifactOverride,\n resolveOverride,\n type ArtifactOverride,\n} from './marketplace-overrides'\n\n/**\n * Publish-time validation for themes as a marketplace artifact (AGL-1020).\n *\n * A plugin bundle is validated because it is code and the risk is a\n * compromised site. A theme is data, so the risk is different in kind but not\n * in consequence: the failure mode is an unreadable or half-painted site, which\n * a publisher cannot see from their own machine because their fonts are\n * installed and their eyes already know what the text says.\n *\n * Both of the things that go wrong are mechanically checkable, so they are\n * checked here rather than left to review:\n *\n * * **Completeness.** Light and dark are not one palette with a filter. A theme\n * that defines only light is half a theme, and installing it leaves every\n * dark-mode visitor on MUI's stock blue — which reads as the site being\n * broken, not as the theme being partial.\n * * **Contrast.** Text on its background, and label on its button, are the two\n * pairs that make a site unusable when they are wrong. WCAG gives an exact\n * ratio, so \"is this readable\" has an answer rather than an opinion.\n *\n * Errors refuse the publish; warnings are shown and can be published past. The\n * split is deliberate — a 3.9:1 heading is a judgement call a designer may\n * legitimately win, but a theme with no dark scheme is not a complete artifact\n * whoever is asking.\n *\n * Pure and MUI-free so the publish route, the console's pre-flight and the\n * tests all reach the same verdict from the same code.\n */\n\nexport interface ThemeValidationIssue {\n /** Dotted path into the theme, so the editor can focus the offending field. */\n path: string\n message: string\n}\n\nexport interface ThemeValidation {\n /** No errors. Warnings do not block. */\n ok: boolean\n errors: ThemeValidationIssue[]\n warnings: ThemeValidationIssue[]\n}\n\n/**\n * WCAG AA for body text. The same 4.5 the accessibility audit uses, rather\n * than a house number, so a theme that passes here passes there.\n */\nexport const CONTRAST_AA = 4.5\n\n/**\n * WCAG AA for large text (18pt+/14pt bold) and UI component boundaries. Button\n * labels are judged against this rather than 4.5 because they are large, short\n * and centred, and holding them to body-text contrast rejects palettes that are\n * genuinely fine.\n */\nexport const CONTRAST_AA_LARGE = 3\n\nconst SCHEMES: HostThemeScheme[] = ['light', 'dark']\n\n/**\n * Font families that need no loading. Anything else is either declared in the\n * theme's own `fonts` array or will silently fall back to the browser's default\n * — which is the failure the issue calls out, so it is at least a warning.\n */\nconst SYSTEM_FAMILIES = new Set(\n [\n 'system-ui',\n 'sans-serif',\n 'serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n '-apple-system',\n 'blinkmacsystemfont',\n 'segoe ui',\n 'roboto',\n 'helvetica',\n 'helvetica neue',\n 'arial',\n 'georgia',\n 'times new roman',\n 'courier new',\n 'inherit',\n 'initial',\n ].map((family) => family.toLowerCase()),\n)\n\n/**\n * Parses a CSS colour into sRGB channels, or null when it is not something we\n * can reason about.\n *\n * `null` means \"not checked\", never \"failed\": a theme is free to use `color-mix`\n * or a CSS variable, and refusing to publish because this parser is narrower\n * than CSS would be a validator inventing a rule the platform does not have.\n */\nexport function parseColor(\n value: unknown,\n): { r: number; g: number; b: number } | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n\n const hex = /^#([0-9a-f]{3,8})$/.exec(text)\n if (hex) {\n const digits = hex[1]\n if (digits.length === 3 || digits.length === 4) {\n return {\n r: parseInt(digits[0] + digits[0], 16),\n g: parseInt(digits[1] + digits[1], 16),\n b: parseInt(digits[2] + digits[2], 16),\n }\n }\n if (digits.length === 6 || digits.length === 8) {\n return {\n r: parseInt(digits.slice(0, 2), 16),\n g: parseInt(digits.slice(2, 4), 16),\n b: parseInt(digits.slice(4, 6), 16),\n }\n }\n return null\n }\n\n const rgb = /^rgba?\\(([^)]+)\\)$/.exec(text)\n if (rgb) {\n const parts = rgb[1].split(/[,/\\s]+/).filter(Boolean)\n if (parts.length < 3) return null\n const channels = parts.slice(0, 3).map((part) =>\n part.endsWith('%')\n ? (Number(part.slice(0, -1)) / 100) * 255\n : Number(part),\n )\n if (channels.some((channel) => !Number.isFinite(channel))) return null\n const [r, g, b] = channels\n return { r, g, b }\n }\n\n if (text === 'white') return { r: 255, g: 255, b: 255 }\n if (text === 'black') return { r: 0, g: 0, b: 0 }\n return null\n}\n\n/** WCAG relative luminance. */\nfunction luminance(channels: { r: number; g: number; b: number }): number {\n const channel = (raw: number) => {\n const value = Math.min(255, Math.max(0, raw)) / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n }\n return (\n 0.2126 * channel(channels.r) +\n 0.7152 * channel(channels.g) +\n 0.0722 * channel(channels.b)\n )\n}\n\n/**\n * WCAG contrast ratio between two colours, or null when either is unparseable.\n * Ranges 1 (identical) to 21 (black on white).\n */\nexport function contrastRatio(a: unknown, b: unknown): number | null {\n const first = parseColor(a)\n const second = parseColor(b)\n if (!first || !second) return null\n const light = Math.max(luminance(first), luminance(second))\n const dark = Math.min(luminance(first), luminance(second))\n return (light + 0.05) / (dark + 0.05)\n}\n\nconst round = (ratio: number) => Math.round(ratio * 10) / 10\n\n/**\n * The colours a scheme must actually define to be usable on its own.\n *\n * Deliberately short. MUI derives an enormous amount from very little, and\n * demanding a full palette would reject good themes; but a scheme that names no\n * primary, no background and no text has not expressed an identity at all — it\n * is an empty object that happens to have a key.\n */\nfunction validateScheme(\n scheme: HostThemeScheme,\n colors: HostThemeSchemeColors | undefined,\n errors: ThemeValidationIssue[],\n warnings: ThemeValidationIssue[],\n): void {\n const at = `colorSchemes.${scheme}`\n if (!colors || !Object.keys(colors).length) {\n errors.push({\n path: at,\n message:\n `This theme defines no ${scheme} scheme. Light and dark are not one ` +\n 'palette with a filter — install it and every visitor in ' +\n `${scheme} mode sees the stock palette instead of your design.`,\n })\n return\n }\n if (!colors.primary?.main) {\n errors.push({\n path: `${at}.primary.main`,\n message: `The ${scheme} scheme has no primary color.`,\n })\n }\n\n const background = colors.background?.default\n const paper = colors.background?.paper\n const text = colors.text?.primary\n const secondaryText = colors.text?.secondary\n\n // Text on background is the pair that decides whether a site can be read at\n // all, so it is the one contrast failure that refuses the publish.\n const bodyContrast = contrastRatio(text, background)\n if (bodyContrast != null && bodyContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.text.primary`,\n message:\n `Body text on the ${scheme} background is ${round(bodyContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum for readable text.`,\n })\n }\n\n const paperContrast = contrastRatio(text, paper)\n if (paperContrast != null && paperContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.background.paper`,\n message:\n `Body text on ${scheme} cards and menus is ${round(paperContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum. Surfaces inherit this everywhere ` +\n 'a Paper is used, which is most of the site.',\n })\n }\n\n // Secondary text is legitimately dimmer by design, so it warns rather than\n // refuses — but silently shipping unreadable captions is worse than saying so.\n const captionContrast = contrastRatio(secondaryText, background)\n if (captionContrast != null && captionContrast < CONTRAST_AA) {\n warnings.push({\n path: `${at}.text.secondary`,\n message:\n `Secondary text on the ${scheme} background is ` +\n `${round(captionContrast)}:1. Captions and helper text will be hard ` +\n 'to read.',\n })\n }\n\n // A button whose label disappears into its own fill. Only checkable when the\n // theme states contrastText — otherwise MUI derives one, and derives it well.\n for (const key of ['primary', 'secondary', 'error'] as const) {\n const color = colors[key]\n const ratio = contrastRatio(color?.contrastText, color?.main)\n if (ratio != null && ratio < CONTRAST_AA_LARGE) {\n warnings.push({\n path: `${at}.${key}.contrastText`,\n message:\n `The ${scheme} ${key} button label is ${round(ratio)}:1 against its ` +\n `own fill, below ${CONTRAST_AA_LARGE}:1. Leave contrast text unset ` +\n 'to let the theme derive a readable one.',\n })\n }\n }\n}\n\n/**\n * Every family named in the typography, split out of its CSS stack.\n *\n * A stack is a fallback chain, so only the FIRST entry is the one the design\n * depends on; the rest exist precisely to be substituted and warning about them\n * would be noise.\n */\nfunction declaredFamilies(theme: HostTheme): string[] {\n const stacks: string[] = []\n if (theme.typography?.fontFamily) stacks.push(theme.typography.fontFamily)\n for (const variant of Object.values(theme.typography?.variants ?? {})) {\n if (variant?.fontFamily) stacks.push(variant.fontFamily)\n }\n const primary: string[] = []\n for (const stack of stacks) {\n const first = stack.split(',')[0]?.trim().replace(/^[\"']|[\"']$/g, '')\n if (first) primary.push(first)\n }\n return primary\n}\n\nfunction validateFonts(\n theme: HostTheme,\n warnings: ThemeValidationIssue[],\n): void {\n const loadable = new Set(\n (theme.fonts ?? [])\n .map((font: HostThemeFont) => font.family?.trim().toLowerCase())\n .filter(Boolean) as string[],\n )\n const seen = new Set<string>()\n for (const family of declaredFamilies(theme)) {\n const key = family.toLowerCase()\n if (seen.has(key) || loadable.has(key) || SYSTEM_FAMILIES.has(key)) continue\n seen.add(key)\n warnings.push({\n path: 'typography.fontFamily',\n message:\n `\"${family}\" is used but not listed in the theme's fonts, so it will ` +\n 'only appear on machines that happen to have it installed. Add it as ' +\n 'a Google font, or fall back to a system family.',\n })\n }\n}\n\n/**\n * Validates a theme for publishing.\n *\n * The same function backs the console's pre-flight and the publish route: a\n * publisher who is told \"this will be refused\" and then has it accepted (or the\n * reverse) stops trusting either, so there is one verdict from one place.\n */\nexport function validateThemeForPublish(\n theme: HostTheme | null | undefined,\n): ThemeValidation {\n const errors: ThemeValidationIssue[] = []\n const warnings: ThemeValidationIssue[] = []\n\n if (!theme || !Object.keys(theme).length) {\n return {\n ok: false,\n errors: [\n {\n path: '',\n message:\n 'This site has no theme customisations yet, so there is nothing ' +\n 'to publish.',\n },\n ],\n warnings,\n }\n }\n\n for (const scheme of SCHEMES) {\n validateScheme(scheme, theme.colorSchemes?.[scheme], errors, warnings)\n }\n validateFonts(theme, warnings)\n\n return { ok: !errors.length, errors, warnings }\n}\n\n/**\n * Every field of a `HostTheme` that is part of the published artifact.\n *\n * Typed as a total record over `keyof HostTheme` on purpose: adding a field to\n * `HostTheme` fails to compile here until someone decides whether it ships. The\n * alternative — a hand-written list of `if` statements — silently drops the new\n * field, and a theme that loses one is not obviously broken, it is subtly\n * wrong. `mixins` arrived exactly that way (AGL-1242, toolbar heights) while\n * this was being written, which is why the check is a type and not a comment.\n *\n * A `false` means \"host-local, never published\". `darkScheme` is the one such\n * field: whether visitors get a dark scheme is a decision about a SITE's\n * content (does it read in dark?), not about the design being published, so a\n * publisher's opt-out must not travel with the theme and an installer's must\n * not read as a divergence from it.\n */\nconst THEME_ARTIFACT_FIELDS: Record<keyof Required<HostTheme>, boolean> = {\n colorSchemes: true,\n typography: true,\n fonts: true,\n shape: true,\n spacing: true,\n mixins: true,\n components: true,\n darkScheme: false,\n}\n\n/**\n * The theme content as published — the artifact and nothing else.\n *\n * What goes in here is what \"reset to the publisher's version\" restores and\n * what the update diff compares against, so it must hold no per-install data:\n * a host document also carries `updatedAt`, `updatedBy` and whatever else the\n * editor writes alongside, and any of those in the content would read as a user\n * edit on the next diff.\n *\n * Empty branches are dropped so two themes that render identically also hash\n * identically — `{fonts: []}` and `{}` must not produce two base snapshots.\n */\nexport function themeArtifactContent(\n theme: HostTheme | null | undefined,\n): HostTheme {\n const content: Record<string, unknown> = {}\n if (!theme) return content\n for (const [key, ships] of Object.entries(THEME_ARTIFACT_FIELDS)) {\n if (!ships) continue\n const value = (theme as Record<string, unknown>)[key]\n if (value == null) continue\n if (Array.isArray(value) ? !value.length : typeof value === 'object'\n ? !Object.keys(value as object).length\n : false) {\n continue\n }\n content[key] = value\n }\n return content\n}\n\n/**\n * A short, human summary of what a theme actually contains, for the install\n * confirmation and the listing card.\n *\n * Installing a theme replaces a site's visual identity, so the confirmation has\n * to say what is about to change in terms someone recognises — \"both schemes,\n * 2 fonts, 4 component styles\" — rather than showing them a JSON blob and\n * asking them to agree to it.\n */\nexport function describeTheme(theme: HostTheme | null | undefined): string[] {\n if (!theme) return []\n const parts: string[] = []\n const schemes = SCHEMES.filter(\n (scheme) => Object.keys(theme.colorSchemes?.[scheme] ?? {}).length,\n )\n if (schemes.length === 2) parts.push('Light and dark schemes')\n else if (schemes.length === 1) parts.push(`${schemes[0]} scheme only`)\n\n const fonts = theme.fonts?.length ?? 0\n if (fonts) parts.push(`${fonts} font${fonts === 1 ? '' : 's'}`)\n if (theme.typography?.variants) {\n const count = Object.keys(theme.typography.variants).length\n if (count) parts.push(`${count} text style${count === 1 ? '' : 's'}`)\n }\n if (theme.shape?.borderRadius != null) {\n parts.push(`${theme.shape.borderRadius}px corners`)\n }\n if (typeof theme.spacing === 'number') parts.push(`${theme.spacing}px spacing`)\n const components = Object.keys(theme.components ?? {}).length\n if (components) {\n parts.push(`${components} component style${components === 1 ? '' : 's'}`)\n }\n return parts\n}\n\n/* ------------------------------------------------------------------------ *\n * The override layer, applied to themes (AGL-1021)\n * ------------------------------------------------------------------------ */\n\n/**\n * The field on a host document holding the site's theme override.\n *\n * A theme is a FIELD on the host, not a document of its own, so it cannot use\n * `ARTIFACT_OVERRIDE_FIELD` — a host may one day override more than one thing,\n * and a bare `overrides` would be the first to claim the name. The override\n * layer is general precisely so a namespaced field works with it unchanged.\n */\nexport const THEME_OVERRIDE_FIELD = 'themeOverride'\n\n/** A host document, as far as the theme layer is concerned. */\nexport interface ThemeHostDocument {\n theme?: HostTheme | null\n themeOverride?: unknown\n themeInstalledFrom?: { sha256?: string | null; listingId?: string } | null\n /** The library theme the site picked (AGL-3404) — see `theme-library`. */\n themeSelection?: unknown\n}\n\n/** Reads the stored theme override off a host document, tolerating junk. */\nexport function readThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ArtifactOverride | undefined {\n // The override layer's reader is keyed to its own field name, so hand it a\n // shim rather than duplicating its validation — which is where the \"a\n // top-level null blanks the artifact\" guard lives.\n return readArtifactOverride({ overrides: host?.themeOverride })\n}\n\n/**\n * The site's effective theme: the marketplace theme (or the site's own) with\n * this site's overrides resolved over it.\n *\n * This is the MIDDLE and TOP of the three layers the issue describes. The\n * bottom — the platform default — is applied further down by\n * `HostThemeProvider`, which layers whatever this returns onto the brand base\n * (AGL-1180) so a theme that omits a value still yields a complete theme\n * rather than an undefined-shaped hole. Doing it there rather than here is not\n * a detail: the default is a runtime MUI theme with FUNCTION style overrides\n * that JSON cannot represent, so a \"default\" merged at this layer would be a\n * lossy copy of the real one.\n *\n * Every surface that renders a site's appearance calls this — the tenant, all\n * four besigner editors, and the document preview — because a theme that\n * resolves differently in the editor than on the site is worse than one that\n * does not resolve at all.\n */\nexport function resolveSiteTheme(\n host: ThemeHostDocument | null | undefined,\n): HostTheme | undefined {\n const base = host?.theme ?? undefined\n const override = readThemeOverride(host)\n if (!override) return base\n return resolveOverride<HostTheme>(base ?? {}, override.patch)\n}\n\n/**\n * Where a site's theme came from (AGL-2938).\n *\n * - `installed` — a marketplace theme. The site's edits are its override\n * patch over the publisher's version, which stays untouched and can still\n * take an update.\n * - `preset` — a built-in theme a plugin contributes (AGL-3404).\n * - `custom` — one of the site's own themes.\n * - `default` — the platform default.\n *\n * Whichever it is, an edit is an override patch over the picked theme and the\n * theme itself is never rewritten (AGL-3404), so every source can be restored.\n *\n * One reading for every surface that needs it — the page that lays out the\n * Theme section, a widget proposing a change there, and a generator deciding\n * what its change will be stored as — so none of them can file a site under\n * a different source than the save path does.\n */\nexport type HostThemeSource = 'installed' | 'preset' | 'custom' | 'default'\n\nconst THEME_SOURCES: readonly HostThemeSource[] = [\n 'installed',\n 'preset',\n 'custom',\n 'default',\n]\n\nexport function hostThemeSource(\n host: ThemeHostDocument | null | undefined,\n): HostThemeSource {\n const picked = (host?.themeSelection as { kind?: unknown } | null | undefined)\n ?.kind\n if (THEME_SOURCES.includes(picked as HostThemeSource)) {\n return picked as HostThemeSource\n }\n if (host?.themeInstalledFrom?.listingId) return 'installed'\n return host?.theme && Object.keys(host.theme).length ? 'custom' : 'default'\n}\n\n/**\n * Was this override authored against the theme currently installed?\n *\n * False after a theme swap, which is the whole reason `baseSha256` is recorded.\n * Overrides deliberately SURVIVE a swap (the issue's call, and the right one —\n * a brand colour still expresses intent when the theme underneath it changes)\n * but surviving silently is what would surprise people, so every surface that\n * shows an override can ask this and say so.\n *\n * True when there is no override, and when either side has no hash to compare:\n * \"we cannot tell\" must not render as \"these are stale\".\n */\nexport function isOverrideForCurrentTheme(\n host: ThemeHostDocument | null | undefined,\n): boolean {\n const override = readThemeOverride(host)\n if (!override?.baseSha256) return true\n const installed = host?.themeInstalledFrom?.sha256\n if (!installed) return true\n return override.baseSha256 === installed\n}\n\n/** One row of the \"what have I changed?\" view. */\nexport interface ThemeOverrideEntry {\n /** Dotted path into the theme, e.g. `colorSchemes.dark.primary.main`. */\n path: string\n /** A human label for the path, for a UI that is not a JSON viewer. */\n label: string\n /** Which scheme this touches, when it touches one. */\n scheme?: HostThemeScheme\n /** The publisher's value, or undefined when the override adds the field. */\n themeValue: unknown\n /** What this site set it to, or undefined when the override removes it. */\n overrideValue: unknown\n}\n\nconst SEGMENT_LABELS: Record<string, string> = {\n darkScheme: 'Dark scheme',\n colorSchemes: 'Color',\n typography: 'Typography',\n components: 'Component',\n shape: 'Shape',\n spacing: 'Spacing',\n fonts: 'Fonts',\n mixins: 'Layout',\n variants: '',\n background: 'background',\n text: 'text',\n main: '',\n contrastText: 'label color',\n borderRadius: 'corner radius',\n fontFamily: 'font',\n}\n\n/**\n * A readable label for a theme path.\n *\n * \"Colour · dark · primary\" beats `colorSchemes.dark.primary.main`, and the\n * difference matters because this list is the answer to \"what have I changed?\"\n * — a question nobody asks wanting to read JSON paths.\n */\nexport function describeThemePath(path: string): string {\n const segments = path.split('.').filter(Boolean)\n const words: string[] = []\n for (const segment of segments) {\n const mapped = SEGMENT_LABELS[segment]\n if (mapped === '') continue\n words.push(mapped ?? segment)\n }\n return words.join(' · ') || 'the whole theme'\n}\n\n/**\n * \"What have I changed?\" — the patch, rendered.\n *\n * Literally a read of the stored override rather than a diff computed at call\n * time, which is the property the whole layer exists to provide: the answer is\n * the thing that was saved, so it cannot disagree with what will be applied.\n */\nexport function describeThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overridePaths(override.patch).map((path) => {\n const segments = path.split('.')\n const scheme =\n segments[0] === 'colorSchemes' &&\n (segments[1] === 'light' || segments[1] === 'dark')\n ? (segments[1] as HostThemeScheme)\n : undefined\n return {\n path,\n label: describeThemePath(path),\n scheme,\n themeValue: readThemePath(base, path),\n overrideValue: readThemePath(resolved, path),\n }\n })\n}\n\n/** Reads a dotted path out of a theme. */\nfunction readThemePath(theme: unknown, path: string): unknown {\n let cursor: unknown = theme\n for (const segment of path.split('.')) {\n if (cursor == null || typeof cursor !== 'object') return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * The override this site should store after the theme editor produced `edited`.\n *\n * The editor edits the RESOLVED theme — that is what it renders and what the\n * user sees — so the patch is the difference between the publisher's base and\n * what came back. Diffing here rather than tracking per-field dirty state means\n * a field the user set and then set back to the theme's value stops being an\n * override, which is what \"reset\" means without needing a reset button to have\n * been pressed.\n */\nexport function themeOverridePatch(\n host: ThemeHostDocument | null | undefined,\n edited: HostTheme,\n): unknown {\n return diffOverride(host?.theme ?? {}, edited)\n}\n\n/**\n * Where this site's overrides sit on top of values a theme update also changes\n * — the only place a conflict can exist.\n *\n * Narrow by construction: an override touching four paths can conflict in at\n * most four places however much of the theme the publisher rewrote. That is the\n * difference between \"the publisher also changed your heading colour, whose\n * wins?\" and \"this update rewrites your theme, continue?\".\n */\nexport function themeUpdateConflicts(\n host: ThemeHostDocument | null | undefined,\n incoming: HostTheme,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override || isEmptyOverride(override.patch)) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overrideConflicts(base, override.patch, incoming).map((path) => ({\n path,\n label: describeThemePath(path),\n scheme:\n path.startsWith('colorSchemes.light.')\n ? ('light' as const)\n : path.startsWith('colorSchemes.dark.')\n ? ('dark' as const)\n : undefined,\n themeValue: readThemePath(incoming, path),\n overrideValue: readThemePath(resolved, path),\n }))\n}\n"],"names":["diffOverride","isEmptyOverride","overrideConflicts","overridePaths","readArtifactOverride","resolveOverride","CONTRAST_AA","CONTRAST_AA_LARGE","SCHEMES","SYSTEM_FAMILIES","Set","map","family","toLowerCase","parseColor","value","text","trim","hex","exec","digits","length","r","parseInt","g","b","slice","rgb","parts","split","filter","Boolean","channels","part","endsWith","Number","some","channel","isFinite","luminance","raw","Math","min","max","contrastRatio","a","first","second","light","dark","round","ratio","validateScheme","scheme","colors","errors","warnings","at","Object","keys","push","path","message","primary","main","background","default","paper","secondaryText","secondary","bodyContrast","paperContrast","captionContrast","key","color","contrastText","declaredFamilies","theme","stacks","typography","fontFamily","variant","values","variants","stack","replace","validateFonts","loadable","fonts","font","seen","has","add","validateThemeForPublish","ok","colorSchemes","THEME_ARTIFACT_FIELDS","shape","spacing","mixins","components","darkScheme","themeArtifactContent","content","ships","entries","Array","isArray","describeTheme","schemes","count","borderRadius","THEME_OVERRIDE_FIELD","readThemeOverride","host","overrides","themeOverride","resolveSiteTheme","base","undefined","override","patch","THEME_SOURCES","hostThemeSource","picked","themeSelection","kind","includes","themeInstalledFrom","listingId","isOverrideForCurrentTheme","baseSha256","installed","sha256","SEGMENT_LABELS","describeThemePath","segments","words","segment","mapped","join","describeThemeOverride","resolved","label","themeValue","readThemePath","overrideValue","cursor","themeOverridePatch","edited","themeUpdateConflicts","incoming","startsWith"],"mappings":"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"}
@@ -139,7 +139,8 @@ export declare const PUBLISHER_AGREEMENT_TITLE = "Marketplace Publisher Agreemen
139
139
  *
140
140
  * TO PUBLISH A CHANGE: publish the besigner edit first, confirm the live page
141
141
  * serves it, capture a new snapshot under the new version's directory, then
142
- * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together. Never
142
+ * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together, and add
143
+ * the version's entry to `PUBLISHER_AGREEMENT_CHANGES` below. Never
143
144
  * hand-write a snapshot: it is evidence of what a publisher was shown, and a
144
145
  * snapshot of unpublished text is a false record.
145
146
  */
@@ -195,6 +196,40 @@ export declare const PUBLISHER_AGREEMENT_POINTS: readonly {
195
196
  label: string;
196
197
  detail: string;
197
198
  }[];
199
+ /**
200
+ * What each version changed from the one before it, in the words a publisher
201
+ * re-accepting needs (AGL-3407).
202
+ *
203
+ * An org that accepted an older version is asked again, and "the terms
204
+ * changed" with nothing more is a request to re-read thirteen sections to
205
+ * find two edited clauses. Each entry names the sections that moved, read off
206
+ * the archived snapshots beside this module (`legal/publisher-agreement/`) —
207
+ * never written from memory of the edit, for the same reason a snapshot is
208
+ * never hand-written.
209
+ *
210
+ * Oldest first. The last entry is always {@link PUBLISHER_AGREEMENT_VERSION}:
211
+ * a bump adds its entry in the same change, and
212
+ * `publisher-agreement-version.spec.ts` fails a bump that did not. The first
213
+ * published version has no entry, because nothing preceded it that anyone
214
+ * accepted.
215
+ */
216
+ export declare const PUBLISHER_AGREEMENT_CHANGES: readonly {
217
+ version: string;
218
+ changes: readonly string[];
219
+ }[];
220
+ /**
221
+ * The changes an org has not yet accepted, oldest first.
222
+ *
223
+ * Versions are dated strings (`YYYY-MM-DD.n`), so they order as text. An
224
+ * acceptance of a version this list does not name — one older than the first
225
+ * entry — gets every entry, which is the honest answer: all of it is new to
226
+ * them. No acceptance at all gets none, because a first acceptance is of the
227
+ * whole document, not a set of edits.
228
+ */
229
+ export declare function publisherAgreementChangesSince(acceptedVersion: string | null | undefined, currentVersion?: string): readonly {
230
+ version: string;
231
+ changes: readonly string[];
232
+ }[];
198
233
  /**
199
234
  * A recorded acceptance: which version, of which exact bytes, by whom, when.
200
235
  *
@@ -154,7 +154,8 @@ export const PUBLISHER_AGREEMENT_TITLE = 'Marketplace Publisher Agreement';
154
154
  *
155
155
  * TO PUBLISH A CHANGE: publish the besigner edit first, confirm the live page
156
156
  * serves it, capture a new snapshot under the new version's directory, then
157
- * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together. Never
157
+ * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together, and add
158
+ * the version's entry to `PUBLISHER_AGREEMENT_CHANGES` below. Never
158
159
  * hand-write a snapshot: it is evidence of what a publisher was shown, and a
159
160
  * snapshot of unpublished text is a false record.
160
161
  */ export const PUBLISHER_AGREEMENT_SHA256 = '2df6e940f037b4630e53c8f63bad8604ef4bf370374b89afcd4ea7f9fe8e8d63';
@@ -238,6 +239,52 @@ export const PUBLISHER_AGREEMENT_TITLE = 'Marketplace Publisher Agreement';
238
239
  detail: 'Including infringement claims and disputes with the organizations that install it.'
239
240
  }
240
241
  ];
242
+ /**
243
+ * What each version changed from the one before it, in the words a publisher
244
+ * re-accepting needs (AGL-3407).
245
+ *
246
+ * An org that accepted an older version is asked again, and "the terms
247
+ * changed" with nothing more is a request to re-read thirteen sections to
248
+ * find two edited clauses. Each entry names the sections that moved, read off
249
+ * the archived snapshots beside this module (`legal/publisher-agreement/`) —
250
+ * never written from memory of the edit, for the same reason a snapshot is
251
+ * never hand-written.
252
+ *
253
+ * Oldest first. The last entry is always {@link PUBLISHER_AGREEMENT_VERSION}:
254
+ * a bump adds its entry in the same change, and
255
+ * `publisher-agreement-version.spec.ts` fails a bump that did not. The first
256
+ * published version has no entry, because nothing preceded it that anyone
257
+ * accepted.
258
+ */ export const PUBLISHER_AGREEMENT_CHANGES = [
259
+ {
260
+ version: '2026-08-18.1',
261
+ changes: [
262
+ '§1 names the marketplace and the End User License Agreement by their current names.',
263
+ '§3(h), new: listing conduct — what an Artifact and its listing may not claim about the platform, its plans or its prices.',
264
+ '§8.6, new: a paid Artifact, tier or license key must be sold through the Marketplace.',
265
+ '§13.6, new: how you may use the platform’s names and marks.'
266
+ ]
267
+ },
268
+ {
269
+ version: '2026-08-24.1',
270
+ changes: [
271
+ '§13.3: venue moves to Travis County, Texas, and governing law now cites Terms of Service Section 18.6.'
272
+ ]
273
+ }
274
+ ];
275
+ /**
276
+ * The changes an org has not yet accepted, oldest first.
277
+ *
278
+ * Versions are dated strings (`YYYY-MM-DD.n`), so they order as text. An
279
+ * acceptance of a version this list does not name — one older than the first
280
+ * entry — gets every entry, which is the honest answer: all of it is new to
281
+ * them. No acceptance at all gets none, because a first acceptance is of the
282
+ * whole document, not a set of edits.
283
+ */ export function publisherAgreementChangesSince(acceptedVersion, currentVersion = PUBLISHER_AGREEMENT_VERSION) {
284
+ const accepted = typeof acceptedVersion === 'string' ? acceptedVersion.trim() : '';
285
+ if (!accepted) return [];
286
+ return PUBLISHER_AGREEMENT_CHANGES.filter((entry)=>entry.version > accepted && entry.version <= currentVersion);
287
+ }
241
288
  export function publisherAgreementState(acceptance, currentVersion = PUBLISHER_AGREEMENT_VERSION) {
242
289
  const accepted = acceptance == null ? void 0 : acceptance.version;
243
290
  if (typeof accepted !== 'string' || !accepted.trim()) return 'none';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/publisher-agreement.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 * The marketplace publisher agreement, and whether an org has accepted the\n * current one (AGL-1077).\n *\n * Deliberately NOT the per-version attestation (AGL-969). An attestation is\n * a statement about *specific bytes* and re-asks on every republish, because\n * the bytes changed. This is about the *relationship*: what we may do with a\n * listing, what the publisher warrants, indemnity, takedown, payouts, what\n * survives termination. It is accepted once, by an org, and re-asked only\n * when we change it.\n *\n * Shared like the attestation model is, and for the same reason: the publish\n * route enforces it, the profile page collects it, and the review page shows\n * staff which terms a publisher is actually under. Three copies of a version\n * string would drift, and a drifted version is one that either re-asks\n * forever or never re-asks at all.\n */\n\nimport { LEGAL_ORIGIN, isPublishedLegalUrl } from './published-legal-pages'\n\n/**\n * The version of the agreement currently in force.\n *\n * Matches the `Agreement version` header of the document itself, which is now\n * besigner content at {@link PUBLISHER_AGREEMENT_URL} rather than a repo file\n * (same practice as the Terms of Service). Bump this **only** when the\n * substance changes in a way a publisher should re-read — a typo fix is not a\n * bump. Bumping it stops every publisher at their next publish until they\n * accept again.\n *\n * Moved off `2026-07-28.1` on 2026-08-14 (AGL-1674). That string named the\n * July draft, which was never published and whose §8 said the publisher is the\n * seller of record. The published text says the opposite — Aglyn processes the\n * sale and remits transaction tax — so it is a different document in\n * substance, not a reissue, and re-asking is the point rather than a cost.\n *\n * Moved to `2026-08-18.1` on 2026-08-18 (AGL-1840 + AGL-1661). Two body-text\n * changes: §1 now says \"the Aglyn marketplace\" (the AGL-975 rename, and the\n * reason this snapshot must NOT need the AGL-975 naming-guard exemption that\n * the `2026-08-14.1` one still carries), and §1's cross-reference to\n * the \"End User Licence Agreement\"\n * becomes \"License\" to match the document that agreement actually is, which\n * was renamed on the same day.\n *\n * ⚠️ Read against the \"a typo fix is not a bump\" rule above, both edits are\n * closer to a typo than to substance, and the tension is real. The bump was\n * taken anyway on the decision (2026-08-18), for a reason the rule does not\n * cover: the served page changed, so keeping `2026-08-14.1` would leave the\n * pin below asserting a hash for text nobody is shown any more — a FALSE\n * RECORD, which is the exact failure the pin exists to detect. The choice is\n * therefore between a bump and a broken pin, not between a bump and nothing.\n *\n * ## 2026-08-22 — the page changed and this string DELIBERATELY did not\n *\n * Three clauses were added on 2026-08-22 (§3(h) listing conduct, §8.6 sales\n * through the Marketplace, §13.6 marks) and the pin below moved with them, but\n * `2026-08-18.1` stayed. That is not the third option the paragraph above says\n * does not exist — it is the same choice with one input changed. A bump exists\n * to re-ask publishers who accepted the OLD text; production holds exactly one\n * acceptance record and it is the own, so there is nobody to re-ask and the\n * bump would buy nothing. the decision, 2026-08-22.\n *\n * The pin still had to move, for precisely the reason given above: the served\n * bytes changed, so leaving the old hash here would assert a document nobody\n * is shown. Version unchanged + pin re-captured is coherent ONLY while the\n * acceptance set is empty. The first real publisher acceptance ends that, and\n * from then on any body-text change costs a bump — see the same rule in\n * `apps/console/constants/legal-documents.ts`, where the clickwrap snapshots\n * collapsed back to `v1` on the identical argument.\n *\n * ## 2026-08-24 — `2026-08-24.1`, and why this one CANNOT be folded (AGL-1956)\n *\n * §13.3 changed twice in one sentence. The venue moved from Williamson County\n * to TRAVIS County (decided) — Williamson entered from the former\n * Jarrell residential address, while every published Aglyn address is 5900\n * Balcones Dr STE 100, Austin, TX 78731, which is Travis. And the clause cited\n * \"Terms of Service Section 19\" for governing law and venue when those live at\n * §18.6; §19 is General. A wrong cross-reference in a forum-selection clause is\n * the kind of defect that gets argued about, so it was corrected in the same\n * pass.\n *\n * Unlike 2026-08-22, this one takes the bump rather than only re-pinning, and\n * the deciding reason is mechanical rather than editorial: THIS VERSION STRING\n * IS PUBLISHED IN THE DOCUMENT. The page at {@link PUBLISHER_AGREEMENT_URL}\n * serves `Agreement version: 2026-08-24.1` in its own second line, so the\n * capture below contains it, and `publisher-agreement-version.spec.ts` asserts\n * the snapshot contains `Agreement version: ${PUBLISHER_AGREEMENT_VERSION}`.\n * Holding the constant at `2026-08-18.1` against that text would need the page\n * republished to say so, and would otherwise record a publisher as having\n * accepted `2026-08-18.1` while naming bytes that call themselves\n * `2026-08-24.1`. That is a false record of exactly the shape the pin exists\n * to prevent.\n *\n * ⚠️ THIS IS THE ONE PLACE THE CLICKWRAP ARGUMENT DOES NOT REACH. The signup\n * clickwrap folded its 2026-08-24 corrections into `v1` and did not bump\n * (`apps/console/constants/legal-documents.ts`) — decided: no v2\n * before launch. That works there because `v1` is a REPO-SIDE label: the\n * published Terms carry a \"Last updated\" date and no version string at all, so\n * the label can stay still while the text moves. Here the label is part of the\n * document a publisher reads. The two schemes are not the same kind of thing\n * and must not be reasoned about as one — that difference is the whole content\n * of this paragraph.\n *\n * On the merits it would have been a bump anyway: a forum-selection clause is\n * substance, not a typo — it names the court a publisher would have to travel\n * to. The 2026-08-22 cost argument is unchanged and still cheap, production\n * holding exactly one acceptance record which is the own.\n *\n * §8.1 and §8.3 are UNTOUCHED and were already correct: they have said Aglyn is\n * merchant of record and determines, collects and remits transaction tax since\n * 2026-08-14. The Terms of Service moved TO them in this pass (new ToS §10.7),\n * not the other way around.\n */\nexport const PUBLISHER_AGREEMENT_VERSION = '2026-08-24.1'\n\nexport const PUBLISHER_AGREEMENT_TITLE = 'Marketplace Publisher Agreement'\n\n/**\n * The snapshot pin for the version above (AGL-1678).\n *\n * A version string is a label we control and can reuse, so on its own it\n * proves nothing about what a publisher actually read. These two values pin\n * the archived snapshot at\n * `legal/publisher-agreement/{PUBLISHER_AGREEMENT_VERSION}/marketplace-publisher-agreement.txt`\n * (beside this module), captured FROM THE LIVE PAGE after publication — the\n * same publication-first machinery as the signup clickwrap\n * (`apps/console/constants/legal-documents.ts`, AGL-1497), and deliberately a\n * PARALLEL manifest rather than an entry in `LEGAL_DOCUMENTS`: ToS §19.1 makes\n * that set the entire signup agreement, and the publisher agreement is\n * deliberately not part of it. Mixing them would widen what a signup\n * acceptance covers.\n *\n * Both values go onto every acceptance record, so a record is self-contained\n * evidence of content and later tampering with the archive is detectable\n * rather than invisible. `publisher-agreement-version.spec.ts` re-hashes the\n * snapshot, so one version string can never come to mean two different\n * documents.\n *\n * Capture method (proven byte-for-byte against the clickwrap v4 hashes before\n * this snapshot was taken): fetch the live page, walk its HTML text nodes\n * skipping script/style, trim each, drop empties, take the document's content\n * block (first line `Last updated: …` through its closing `© …` line,\n * excluding site chrome and the table of contents), join with `\\n`, and end\n * with a trailing newline. UTF-8 throughout.\n *\n * TO PUBLISH A CHANGE: publish the besigner edit first, confirm the live page\n * serves it, capture a new snapshot under the new version's directory, then\n * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together. Never\n * hand-write a snapshot: it is evidence of what a publisher was shown, and a\n * snapshot of unpublished text is a false record.\n */\nexport const PUBLISHER_AGREEMENT_SHA256 =\n '2df6e940f037b4630e53c8f63bad8604ef4bf370374b89afcd4ea7f9fe8e8d63'\n\n/** Byte length of the same snapshot — a cheap second check on the content. */\nexport const PUBLISHER_AGREEMENT_BYTES = 13730\n\n/**\n * Canonical document, on the operator's legal origin beside the other terms.\n *\n * Built from `LEGAL_ORIGIN` (AGL-2196), which this module already depends on:\n * `publisherAgreementIsPublished` below calls `isPublishedLegalUrl`, and that\n * accepts ONLY `LEGAL_ORIGIN`. So the gate was operator-aware while the link\n * it gates was a bare `aglyn.com` literal — meaning on a self-host install\n * with `NEXT_PUBLIC_OPERATOR_LEGAL_ORIGIN` set, the gate rejected the very URL\n * this file handed it, and every publisher was blocked from accepting. One\n * origin for both closes that, and leaves Aglyn's own URL byte-identical.\n */\nexport const PUBLISHER_AGREEMENT_URL = `${LEGAL_ORIGIN}/legal/marketplace-publisher-agreement`\n\n/**\n * Whether that document can actually be read (AGL-1660).\n *\n * It could not, from the day this gate shipped until the day this check did:\n * the URL above 404ed, and the console still rendered it as \"Read the full\n * Marketplace Publisher Agreement\" above an Accept button. An acceptance\n * collected that way is a signature on a blank page — the same\n * publication-first principle the clickwrap snapshots are built on, failing in\n * the one place the text was never published at all.\n *\n * Derived, not a second flag: it reads `PUBLISHED_LEGAL_PATHS`, so the ONE\n * edit that adds the path when the page goes live is what re-opens acceptance\n * everywhere. There is no boolean to forget to flip, and no way for the link\n * and the gate to disagree.\n */\nexport function publisherAgreementIsPublished(\n url: string = PUBLISHER_AGREEMENT_URL,\n): boolean {\n return isPublishedLegalUrl(url)\n}\n\n/**\n * What a publisher is told while the document has no page.\n *\n * Deliberately not \"try again later\" — it names the reason, because the fix is\n * ours and the publisher can do nothing about it. It must never read as though\n * accepting is something they merely have not got round to.\n */\nexport const PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE =\n `The ${PUBLISHER_AGREEMENT_TITLE} is not published yet, so there is ` +\n 'nothing here for you to read — and we will not ask you to agree to a ' +\n 'document we have not shown you. Acceptance is paused until it is ' +\n 'available, which also means marketplace publishing is paused. Nothing is ' +\n 'required of you in the meantime.'\n\n/**\n * What the agreement actually commits a publisher to.\n *\n * Shown above the accept control. A link alone is the pattern that produces\n * agreements nobody has read; these are the terms most likely to surprise\n * someone later, in the words they would need at that moment. They summarise\n * the document — they are not it, and the copy says so.\n */\nexport const PUBLISHER_AGREEMENT_POINTS: readonly {\n id: string\n label: string\n detail: string\n}[] = [\n {\n id: 'hosting-license',\n label: 'We may host, verify and distribute what you publish',\n detail:\n 'Including keeping an immutable copy of every version, inspecting its code, and showing your listing content in the marketplace and console.',\n },\n {\n id: 'warranties',\n label: 'You warrant each version as you publish it',\n detail:\n 'That you have the right to publish it, that its license is accurate and compatible, that it carries no malware or hidden telemetry, and that its documentation describes what it does with data.',\n },\n {\n id: 'immutable',\n label: 'You cannot recall code that is already installed',\n detail:\n 'Installs pin an exact version and hash. A mistake ships until an installer chooses to upgrade — publishing a new version is the only remedy you have.',\n },\n {\n id: 'revocation',\n label: 'We can disable a version everywhere, without notice',\n detail:\n 'If a version looks like a security, legal or privacy risk we stop it on every site running it, and tell you afterwards. Operating a platform that executes other people’s code requires this.',\n },\n {\n id: 'review',\n label: 'Review is a safety screen, not an endorsement',\n detail:\n 'Approval is not an audit and not a guarantee your plugin works. We may decline, delist or unsign anything, for reasons that may have nothing to do with your code.',\n },\n {\n id: 'payouts',\n label: 'On paid listings we process the sale and take a fee',\n detail:\n 'We process the purchase and pay your share into your own Stripe Connect account, less a platform fee that may change. We collect and remit sales tax on Marketplace sales; refunds, chargebacks and support on your Artifacts are yours.',\n },\n {\n id: 'indemnity',\n label: 'You indemnify us for claims arising from what you publish',\n detail:\n 'Including infringement claims and disputes with the organizations that install it.',\n },\n]\n\n/**\n * A recorded acceptance: which version, of which exact bytes, by whom, when.\n *\n * `sha256`/`bytes` (AGL-1678) pin the acceptance to the archived snapshot of\n * the text the publisher was shown. Optional because records written before\n * the pin existed carry only the version string — absence means \"accepted\n * before 2026-08-17\", not \"accepted nothing\".\n */\nexport interface PublisherAgreementAcceptance {\n version?: string\n sha256?: string\n bytes?: number\n acceptedBy?: string\n acceptedAt?: unknown\n}\n\n/**\n * Where an org stands with the agreement.\n *\n * `none` and `outdated` are deliberately different answers. \"You have never\n * agreed to anything\" and \"the terms changed since you agreed\" are different\n * situations with different copy, and collapsing them into a boolean is how\n * a re-acceptance ends up reading like a first-time setup step.\n */\nexport type PublisherAgreementState = 'none' | 'outdated' | 'current'\n\nexport function publisherAgreementState(\n acceptance: PublisherAgreementAcceptance | null | undefined,\n currentVersion: string = PUBLISHER_AGREEMENT_VERSION,\n): PublisherAgreementState {\n const accepted = acceptance?.version\n if (typeof accepted !== 'string' || !accepted.trim()) return 'none'\n return accepted === currentVersion ? 'current' : 'outdated'\n}\n\n/**\n * Whether a publish may proceed.\n *\n * An older acceptance is NOT carried forward. Whoever publishes next reads\n * the changed agreement and accepts it, or does not publish — silently\n * inheriting an acceptance across a rewrite is precisely what makes the\n * record worthless.\n */\nexport function hasCurrentPublisherAgreement(\n acceptance: PublisherAgreementAcceptance | null | undefined,\n currentVersion: string = PUBLISHER_AGREEMENT_VERSION,\n): boolean {\n return publisherAgreementState(acceptance, currentVersion) === 'current'\n}\n\n/**\n * What the acceptance UI may show (AGL-1660).\n *\n * The gate lives HERE rather than in the panel, for the reason every \"check it\n * in the component\" control eventually fails: the component is not the only\n * caller. The accept route consults the same function, so a client that skips\n * the UI entirely meets the same answer.\n *\n * `documentUrl` is null, not the URL, when the document is unpublished — the\n * console reads the link target from this, so an unreadable document is\n * structurally unable to reach an href.\n */\nexport interface PublisherAgreementPresentation {\n /** The document to link, or null when there is no page to link to. */\n documentUrl: string | null\n /** Whether the accept control may be offered at all. */\n canAccept: boolean\n /** Why acceptance is unavailable, or null when it is available. */\n unavailableNotice: string | null\n}\n\nexport function publisherAgreementPresentation(\n state: PublisherAgreementState,\n published: boolean = publisherAgreementIsPublished(),\n): PublisherAgreementPresentation {\n return {\n documentUrl: published ? PUBLISHER_AGREEMENT_URL : null,\n // Already current is not \"cannot accept because unpublished\" — it is\n // nothing left to do, and the panel says so separately.\n canAccept: published && state !== 'current',\n unavailableNotice: published ? null : PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE,\n }\n}\n\n/** The refusal a publisher sees, saying which of the two problems it is. */\nexport function publisherAgreementRefusal(\n state: PublisherAgreementState,\n published: boolean = publisherAgreementIsPublished(),\n): string {\n // Sending someone to a control we have disabled is worse than saying\n // nothing: they go, find no button, and conclude the console is broken.\n if (!published) {\n return (\n `The ${PUBLISHER_AGREEMENT_TITLE} is not published yet, so it cannot ` +\n 'be accepted and publishing is paused for everyone. This is on us, not ' +\n 'on your organization.'\n )\n }\n return state === 'outdated'\n ? `The ${PUBLISHER_AGREEMENT_TITLE} has changed since your organization ` +\n 'accepted it. Review and accept the current version in Marketplace → ' +\n 'Publisher Profile, then publish.'\n : `Your organization has not accepted the ${PUBLISHER_AGREEMENT_TITLE}. ` +\n 'Accept it in Marketplace → Publisher Profile, then publish.'\n}\n"],"names":["LEGAL_ORIGIN","isPublishedLegalUrl","PUBLISHER_AGREEMENT_VERSION","PUBLISHER_AGREEMENT_TITLE","PUBLISHER_AGREEMENT_SHA256","PUBLISHER_AGREEMENT_BYTES","PUBLISHER_AGREEMENT_URL","publisherAgreementIsPublished","url","PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE","PUBLISHER_AGREEMENT_POINTS","id","label","detail","publisherAgreementState","acceptance","currentVersion","accepted","version","trim","hasCurrentPublisherAgreement","publisherAgreementPresentation","state","published","documentUrl","canAccept","unavailableNotice","publisherAgreementRefusal"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;CAgBC,GAED,SAASA,YAAY,EAAEC,mBAAmB,QAAQ,6BAAyB;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FC,GACD,OAAO,MAAMC,8BAA8B,eAAc;AAEzD,OAAO,MAAMC,4BAA4B,kCAAiC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GACD,OAAO,MAAMC,6BACX,mEAAkE;AAEpE,4EAA4E,GAC5E,OAAO,MAAMC,4BAA4B,MAAK;AAE9C;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,0BAA0B,GAAGN,aAAa,sCAAsC,CAAC,CAAA;AAE9F;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASO,8BACdC,MAAcF,uBAAuB;IAErC,OAAOL,oBAAoBO;AAC7B;AAEA;;;;;;CAMC,GACD,OAAO,MAAMC,yCACX,CAAC,IAAI,EAAEN,0BAA0B,mCAAmC,CAAC,GACrE,0EACA,sEACA,8EACA,mCAAkC;AAEpC;;;;;;;CAOC,GACD,OAAO,MAAMO,6BAIP;IACJ;QACEC,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;CACD,CAAA;AA4BD,OAAO,SAASC,wBACdC,UAA2D,EAC3DC,iBAAyBd,2BAA2B;IAEpD,MAAMe,WAAWF,8BAAAA,WAAYG,OAAO;IACpC,IAAI,OAAOD,aAAa,YAAY,CAACA,SAASE,IAAI,IAAI,OAAO;IAC7D,OAAOF,aAAaD,iBAAiB,YAAY;AACnD;AAEA;;;;;;;CAOC,GACD,OAAO,SAASI,6BACdL,UAA2D,EAC3DC,iBAAyBd,2BAA2B;IAEpD,OAAOY,wBAAwBC,YAAYC,oBAAoB;AACjE;AAuBA,OAAO,SAASK,+BACdC,KAA8B,EAC9BC,YAAqBhB,+BAA+B;IAEpD,OAAO;QACLiB,aAAaD,YAAYjB,0BAA0B;QACnD,qEAAqE;QACrE,wDAAwD;QACxDmB,WAAWF,aAAaD,UAAU;QAClCI,mBAAmBH,YAAY,OAAOd;IACxC;AACF;AAEA,0EAA0E,GAC1E,OAAO,SAASkB,0BACdL,KAA8B,EAC9BC,YAAqBhB,+BAA+B;IAEpD,qEAAqE;IACrE,wEAAwE;IACxE,IAAI,CAACgB,WAAW;QACd,OACE,CAAC,IAAI,EAAEpB,0BAA0B,oCAAoC,CAAC,GACtE,2EACA;IAEJ;IACA,OAAOmB,UAAU,aACb,CAAC,IAAI,EAAEnB,0BAA0B,qCAAqC,CAAC,GACrE,yEACA,qCACF,CAAC,uCAAuC,EAAEA,0BAA0B,EAAE,CAAC,GACrE;AACR"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/publisher-agreement.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 * The marketplace publisher agreement, and whether an org has accepted the\n * current one (AGL-1077).\n *\n * Deliberately NOT the per-version attestation (AGL-969). An attestation is\n * a statement about *specific bytes* and re-asks on every republish, because\n * the bytes changed. This is about the *relationship*: what we may do with a\n * listing, what the publisher warrants, indemnity, takedown, payouts, what\n * survives termination. It is accepted once, by an org, and re-asked only\n * when we change it.\n *\n * Shared like the attestation model is, and for the same reason: the publish\n * route enforces it, the profile page collects it, and the review page shows\n * staff which terms a publisher is actually under. Three copies of a version\n * string would drift, and a drifted version is one that either re-asks\n * forever or never re-asks at all.\n */\n\nimport { LEGAL_ORIGIN, isPublishedLegalUrl } from './published-legal-pages'\n\n/**\n * The version of the agreement currently in force.\n *\n * Matches the `Agreement version` header of the document itself, which is now\n * besigner content at {@link PUBLISHER_AGREEMENT_URL} rather than a repo file\n * (same practice as the Terms of Service). Bump this **only** when the\n * substance changes in a way a publisher should re-read — a typo fix is not a\n * bump. Bumping it stops every publisher at their next publish until they\n * accept again.\n *\n * Moved off `2026-07-28.1` on 2026-08-14 (AGL-1674). That string named the\n * July draft, which was never published and whose §8 said the publisher is the\n * seller of record. The published text says the opposite — Aglyn processes the\n * sale and remits transaction tax — so it is a different document in\n * substance, not a reissue, and re-asking is the point rather than a cost.\n *\n * Moved to `2026-08-18.1` on 2026-08-18 (AGL-1840 + AGL-1661). Two body-text\n * changes: §1 now says \"the Aglyn marketplace\" (the AGL-975 rename, and the\n * reason this snapshot must NOT need the AGL-975 naming-guard exemption that\n * the `2026-08-14.1` one still carries), and §1's cross-reference to\n * the \"End User Licence Agreement\"\n * becomes \"License\" to match the document that agreement actually is, which\n * was renamed on the same day.\n *\n * ⚠️ Read against the \"a typo fix is not a bump\" rule above, both edits are\n * closer to a typo than to substance, and the tension is real. The bump was\n * taken anyway on the decision (2026-08-18), for a reason the rule does not\n * cover: the served page changed, so keeping `2026-08-14.1` would leave the\n * pin below asserting a hash for text nobody is shown any more — a FALSE\n * RECORD, which is the exact failure the pin exists to detect. The choice is\n * therefore between a bump and a broken pin, not between a bump and nothing.\n *\n * ## 2026-08-22 — the page changed and this string DELIBERATELY did not\n *\n * Three clauses were added on 2026-08-22 (§3(h) listing conduct, §8.6 sales\n * through the Marketplace, §13.6 marks) and the pin below moved with them, but\n * `2026-08-18.1` stayed. That is not the third option the paragraph above says\n * does not exist — it is the same choice with one input changed. A bump exists\n * to re-ask publishers who accepted the OLD text; production holds exactly one\n * acceptance record and it is the own, so there is nobody to re-ask and the\n * bump would buy nothing. the decision, 2026-08-22.\n *\n * The pin still had to move, for precisely the reason given above: the served\n * bytes changed, so leaving the old hash here would assert a document nobody\n * is shown. Version unchanged + pin re-captured is coherent ONLY while the\n * acceptance set is empty. The first real publisher acceptance ends that, and\n * from then on any body-text change costs a bump — see the same rule in\n * `apps/console/constants/legal-documents.ts`, where the clickwrap snapshots\n * collapsed back to `v1` on the identical argument.\n *\n * ## 2026-08-24 — `2026-08-24.1`, and why this one CANNOT be folded (AGL-1956)\n *\n * §13.3 changed twice in one sentence. The venue moved from Williamson County\n * to TRAVIS County (decided) — Williamson entered from the former\n * Jarrell residential address, while every published Aglyn address is 5900\n * Balcones Dr STE 100, Austin, TX 78731, which is Travis. And the clause cited\n * \"Terms of Service Section 19\" for governing law and venue when those live at\n * §18.6; §19 is General. A wrong cross-reference in a forum-selection clause is\n * the kind of defect that gets argued about, so it was corrected in the same\n * pass.\n *\n * Unlike 2026-08-22, this one takes the bump rather than only re-pinning, and\n * the deciding reason is mechanical rather than editorial: THIS VERSION STRING\n * IS PUBLISHED IN THE DOCUMENT. The page at {@link PUBLISHER_AGREEMENT_URL}\n * serves `Agreement version: 2026-08-24.1` in its own second line, so the\n * capture below contains it, and `publisher-agreement-version.spec.ts` asserts\n * the snapshot contains `Agreement version: ${PUBLISHER_AGREEMENT_VERSION}`.\n * Holding the constant at `2026-08-18.1` against that text would need the page\n * republished to say so, and would otherwise record a publisher as having\n * accepted `2026-08-18.1` while naming bytes that call themselves\n * `2026-08-24.1`. That is a false record of exactly the shape the pin exists\n * to prevent.\n *\n * ⚠️ THIS IS THE ONE PLACE THE CLICKWRAP ARGUMENT DOES NOT REACH. The signup\n * clickwrap folded its 2026-08-24 corrections into `v1` and did not bump\n * (`apps/console/constants/legal-documents.ts`) — decided: no v2\n * before launch. That works there because `v1` is a REPO-SIDE label: the\n * published Terms carry a \"Last updated\" date and no version string at all, so\n * the label can stay still while the text moves. Here the label is part of the\n * document a publisher reads. The two schemes are not the same kind of thing\n * and must not be reasoned about as one — that difference is the whole content\n * of this paragraph.\n *\n * On the merits it would have been a bump anyway: a forum-selection clause is\n * substance, not a typo — it names the court a publisher would have to travel\n * to. The 2026-08-22 cost argument is unchanged and still cheap, production\n * holding exactly one acceptance record which is the own.\n *\n * §8.1 and §8.3 are UNTOUCHED and were already correct: they have said Aglyn is\n * merchant of record and determines, collects and remits transaction tax since\n * 2026-08-14. The Terms of Service moved TO them in this pass (new ToS §10.7),\n * not the other way around.\n */\nexport const PUBLISHER_AGREEMENT_VERSION = '2026-08-24.1'\n\nexport const PUBLISHER_AGREEMENT_TITLE = 'Marketplace Publisher Agreement'\n\n/**\n * The snapshot pin for the version above (AGL-1678).\n *\n * A version string is a label we control and can reuse, so on its own it\n * proves nothing about what a publisher actually read. These two values pin\n * the archived snapshot at\n * `legal/publisher-agreement/{PUBLISHER_AGREEMENT_VERSION}/marketplace-publisher-agreement.txt`\n * (beside this module), captured FROM THE LIVE PAGE after publication — the\n * same publication-first machinery as the signup clickwrap\n * (`apps/console/constants/legal-documents.ts`, AGL-1497), and deliberately a\n * PARALLEL manifest rather than an entry in `LEGAL_DOCUMENTS`: ToS §19.1 makes\n * that set the entire signup agreement, and the publisher agreement is\n * deliberately not part of it. Mixing them would widen what a signup\n * acceptance covers.\n *\n * Both values go onto every acceptance record, so a record is self-contained\n * evidence of content and later tampering with the archive is detectable\n * rather than invisible. `publisher-agreement-version.spec.ts` re-hashes the\n * snapshot, so one version string can never come to mean two different\n * documents.\n *\n * Capture method (proven byte-for-byte against the clickwrap v4 hashes before\n * this snapshot was taken): fetch the live page, walk its HTML text nodes\n * skipping script/style, trim each, drop empties, take the document's content\n * block (first line `Last updated: …` through its closing `© …` line,\n * excluding site chrome and the table of contents), join with `\\n`, and end\n * with a trailing newline. UTF-8 throughout.\n *\n * TO PUBLISH A CHANGE: publish the besigner edit first, confirm the live page\n * serves it, capture a new snapshot under the new version's directory, then\n * bump `PUBLISHER_AGREEMENT_VERSION` and these two values together, and add\n * the version's entry to `PUBLISHER_AGREEMENT_CHANGES` below. Never\n * hand-write a snapshot: it is evidence of what a publisher was shown, and a\n * snapshot of unpublished text is a false record.\n */\nexport const PUBLISHER_AGREEMENT_SHA256 =\n '2df6e940f037b4630e53c8f63bad8604ef4bf370374b89afcd4ea7f9fe8e8d63'\n\n/** Byte length of the same snapshot — a cheap second check on the content. */\nexport const PUBLISHER_AGREEMENT_BYTES = 13730\n\n/**\n * Canonical document, on the operator's legal origin beside the other terms.\n *\n * Built from `LEGAL_ORIGIN` (AGL-2196), which this module already depends on:\n * `publisherAgreementIsPublished` below calls `isPublishedLegalUrl`, and that\n * accepts ONLY `LEGAL_ORIGIN`. So the gate was operator-aware while the link\n * it gates was a bare `aglyn.com` literal — meaning on a self-host install\n * with `NEXT_PUBLIC_OPERATOR_LEGAL_ORIGIN` set, the gate rejected the very URL\n * this file handed it, and every publisher was blocked from accepting. One\n * origin for both closes that, and leaves Aglyn's own URL byte-identical.\n */\nexport const PUBLISHER_AGREEMENT_URL = `${LEGAL_ORIGIN}/legal/marketplace-publisher-agreement`\n\n/**\n * Whether that document can actually be read (AGL-1660).\n *\n * It could not, from the day this gate shipped until the day this check did:\n * the URL above 404ed, and the console still rendered it as \"Read the full\n * Marketplace Publisher Agreement\" above an Accept button. An acceptance\n * collected that way is a signature on a blank page — the same\n * publication-first principle the clickwrap snapshots are built on, failing in\n * the one place the text was never published at all.\n *\n * Derived, not a second flag: it reads `PUBLISHED_LEGAL_PATHS`, so the ONE\n * edit that adds the path when the page goes live is what re-opens acceptance\n * everywhere. There is no boolean to forget to flip, and no way for the link\n * and the gate to disagree.\n */\nexport function publisherAgreementIsPublished(\n url: string = PUBLISHER_AGREEMENT_URL,\n): boolean {\n return isPublishedLegalUrl(url)\n}\n\n/**\n * What a publisher is told while the document has no page.\n *\n * Deliberately not \"try again later\" — it names the reason, because the fix is\n * ours and the publisher can do nothing about it. It must never read as though\n * accepting is something they merely have not got round to.\n */\nexport const PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE =\n `The ${PUBLISHER_AGREEMENT_TITLE} is not published yet, so there is ` +\n 'nothing here for you to read — and we will not ask you to agree to a ' +\n 'document we have not shown you. Acceptance is paused until it is ' +\n 'available, which also means marketplace publishing is paused. Nothing is ' +\n 'required of you in the meantime.'\n\n/**\n * What the agreement actually commits a publisher to.\n *\n * Shown above the accept control. A link alone is the pattern that produces\n * agreements nobody has read; these are the terms most likely to surprise\n * someone later, in the words they would need at that moment. They summarise\n * the document — they are not it, and the copy says so.\n */\nexport const PUBLISHER_AGREEMENT_POINTS: readonly {\n id: string\n label: string\n detail: string\n}[] = [\n {\n id: 'hosting-license',\n label: 'We may host, verify and distribute what you publish',\n detail:\n 'Including keeping an immutable copy of every version, inspecting its code, and showing your listing content in the marketplace and console.',\n },\n {\n id: 'warranties',\n label: 'You warrant each version as you publish it',\n detail:\n 'That you have the right to publish it, that its license is accurate and compatible, that it carries no malware or hidden telemetry, and that its documentation describes what it does with data.',\n },\n {\n id: 'immutable',\n label: 'You cannot recall code that is already installed',\n detail:\n 'Installs pin an exact version and hash. A mistake ships until an installer chooses to upgrade — publishing a new version is the only remedy you have.',\n },\n {\n id: 'revocation',\n label: 'We can disable a version everywhere, without notice',\n detail:\n 'If a version looks like a security, legal or privacy risk we stop it on every site running it, and tell you afterwards. Operating a platform that executes other people’s code requires this.',\n },\n {\n id: 'review',\n label: 'Review is a safety screen, not an endorsement',\n detail:\n 'Approval is not an audit and not a guarantee your plugin works. We may decline, delist or unsign anything, for reasons that may have nothing to do with your code.',\n },\n {\n id: 'payouts',\n label: 'On paid listings we process the sale and take a fee',\n detail:\n 'We process the purchase and pay your share into your own Stripe Connect account, less a platform fee that may change. We collect and remit sales tax on Marketplace sales; refunds, chargebacks and support on your Artifacts are yours.',\n },\n {\n id: 'indemnity',\n label: 'You indemnify us for claims arising from what you publish',\n detail:\n 'Including infringement claims and disputes with the organizations that install it.',\n },\n]\n\n/**\n * What each version changed from the one before it, in the words a publisher\n * re-accepting needs (AGL-3407).\n *\n * An org that accepted an older version is asked again, and \"the terms\n * changed\" with nothing more is a request to re-read thirteen sections to\n * find two edited clauses. Each entry names the sections that moved, read off\n * the archived snapshots beside this module (`legal/publisher-agreement/`) —\n * never written from memory of the edit, for the same reason a snapshot is\n * never hand-written.\n *\n * Oldest first. The last entry is always {@link PUBLISHER_AGREEMENT_VERSION}:\n * a bump adds its entry in the same change, and\n * `publisher-agreement-version.spec.ts` fails a bump that did not. The first\n * published version has no entry, because nothing preceded it that anyone\n * accepted.\n */\nexport const PUBLISHER_AGREEMENT_CHANGES: readonly {\n version: string\n changes: readonly string[]\n}[] = [\n {\n version: '2026-08-18.1',\n changes: [\n '§1 names the marketplace and the End User License Agreement by their current names.',\n '§3(h), new: listing conduct — what an Artifact and its listing may not claim about the platform, its plans or its prices.',\n '§8.6, new: a paid Artifact, tier or license key must be sold through the Marketplace.',\n '§13.6, new: how you may use the platform’s names and marks.',\n ],\n },\n {\n version: '2026-08-24.1',\n changes: [\n '§13.3: venue moves to Travis County, Texas, and governing law now cites Terms of Service Section 18.6.',\n ],\n },\n]\n\n/**\n * The changes an org has not yet accepted, oldest first.\n *\n * Versions are dated strings (`YYYY-MM-DD.n`), so they order as text. An\n * acceptance of a version this list does not name — one older than the first\n * entry — gets every entry, which is the honest answer: all of it is new to\n * them. No acceptance at all gets none, because a first acceptance is of the\n * whole document, not a set of edits.\n */\nexport function publisherAgreementChangesSince(\n acceptedVersion: string | null | undefined,\n currentVersion: string = PUBLISHER_AGREEMENT_VERSION,\n): readonly { version: string; changes: readonly string[] }[] {\n const accepted = typeof acceptedVersion === 'string' ? acceptedVersion.trim() : ''\n if (!accepted) return []\n return PUBLISHER_AGREEMENT_CHANGES.filter(\n (entry) => entry.version > accepted && entry.version <= currentVersion,\n )\n}\n\n/**\n * A recorded acceptance: which version, of which exact bytes, by whom, when.\n *\n * `sha256`/`bytes` (AGL-1678) pin the acceptance to the archived snapshot of\n * the text the publisher was shown. Optional because records written before\n * the pin existed carry only the version string — absence means \"accepted\n * before 2026-08-17\", not \"accepted nothing\".\n */\nexport interface PublisherAgreementAcceptance {\n version?: string\n sha256?: string\n bytes?: number\n acceptedBy?: string\n acceptedAt?: unknown\n}\n\n/**\n * Where an org stands with the agreement.\n *\n * `none` and `outdated` are deliberately different answers. \"You have never\n * agreed to anything\" and \"the terms changed since you agreed\" are different\n * situations with different copy, and collapsing them into a boolean is how\n * a re-acceptance ends up reading like a first-time setup step.\n */\nexport type PublisherAgreementState = 'none' | 'outdated' | 'current'\n\nexport function publisherAgreementState(\n acceptance: PublisherAgreementAcceptance | null | undefined,\n currentVersion: string = PUBLISHER_AGREEMENT_VERSION,\n): PublisherAgreementState {\n const accepted = acceptance?.version\n if (typeof accepted !== 'string' || !accepted.trim()) return 'none'\n return accepted === currentVersion ? 'current' : 'outdated'\n}\n\n/**\n * Whether a publish may proceed.\n *\n * An older acceptance is NOT carried forward. Whoever publishes next reads\n * the changed agreement and accepts it, or does not publish — silently\n * inheriting an acceptance across a rewrite is precisely what makes the\n * record worthless.\n */\nexport function hasCurrentPublisherAgreement(\n acceptance: PublisherAgreementAcceptance | null | undefined,\n currentVersion: string = PUBLISHER_AGREEMENT_VERSION,\n): boolean {\n return publisherAgreementState(acceptance, currentVersion) === 'current'\n}\n\n/**\n * What the acceptance UI may show (AGL-1660).\n *\n * The gate lives HERE rather than in the panel, for the reason every \"check it\n * in the component\" control eventually fails: the component is not the only\n * caller. The accept route consults the same function, so a client that skips\n * the UI entirely meets the same answer.\n *\n * `documentUrl` is null, not the URL, when the document is unpublished — the\n * console reads the link target from this, so an unreadable document is\n * structurally unable to reach an href.\n */\nexport interface PublisherAgreementPresentation {\n /** The document to link, or null when there is no page to link to. */\n documentUrl: string | null\n /** Whether the accept control may be offered at all. */\n canAccept: boolean\n /** Why acceptance is unavailable, or null when it is available. */\n unavailableNotice: string | null\n}\n\nexport function publisherAgreementPresentation(\n state: PublisherAgreementState,\n published: boolean = publisherAgreementIsPublished(),\n): PublisherAgreementPresentation {\n return {\n documentUrl: published ? PUBLISHER_AGREEMENT_URL : null,\n // Already current is not \"cannot accept because unpublished\" — it is\n // nothing left to do, and the panel says so separately.\n canAccept: published && state !== 'current',\n unavailableNotice: published ? null : PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE,\n }\n}\n\n/** The refusal a publisher sees, saying which of the two problems it is. */\nexport function publisherAgreementRefusal(\n state: PublisherAgreementState,\n published: boolean = publisherAgreementIsPublished(),\n): string {\n // Sending someone to a control we have disabled is worse than saying\n // nothing: they go, find no button, and conclude the console is broken.\n if (!published) {\n return (\n `The ${PUBLISHER_AGREEMENT_TITLE} is not published yet, so it cannot ` +\n 'be accepted and publishing is paused for everyone. This is on us, not ' +\n 'on your organization.'\n )\n }\n return state === 'outdated'\n ? `The ${PUBLISHER_AGREEMENT_TITLE} has changed since your organization ` +\n 'accepted it. Review and accept the current version in Marketplace → ' +\n 'Publisher Profile, then publish.'\n : `Your organization has not accepted the ${PUBLISHER_AGREEMENT_TITLE}. ` +\n 'Accept it in Marketplace → Publisher Profile, then publish.'\n}\n"],"names":["LEGAL_ORIGIN","isPublishedLegalUrl","PUBLISHER_AGREEMENT_VERSION","PUBLISHER_AGREEMENT_TITLE","PUBLISHER_AGREEMENT_SHA256","PUBLISHER_AGREEMENT_BYTES","PUBLISHER_AGREEMENT_URL","publisherAgreementIsPublished","url","PUBLISHER_AGREEMENT_UNAVAILABLE_NOTICE","PUBLISHER_AGREEMENT_POINTS","id","label","detail","PUBLISHER_AGREEMENT_CHANGES","version","changes","publisherAgreementChangesSince","acceptedVersion","currentVersion","accepted","trim","filter","entry","publisherAgreementState","acceptance","hasCurrentPublisherAgreement","publisherAgreementPresentation","state","published","documentUrl","canAccept","unavailableNotice","publisherAgreementRefusal"],"mappings":"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"}
@@ -34,6 +34,7 @@
34
34
  "RICH_TEXT_COMMANDS",
35
35
  "ScreenLinkContext",
36
36
  "SiteContext",
37
+ "THEME_PRESETS_LOAD_POINT",
37
38
  "VARIABLE_ID_TOKEN_PATTERN",
38
39
  "VARIABLE_NAME_PATTERN",
39
40
  "aglyn",
@@ -66,6 +67,7 @@
66
67
  "listConsoleOrgNavItems",
67
68
  "listConsoleProviders",
68
69
  "listConsoleStaffPages",
70
+ "listConsoleThemePresets",
69
71
  "listConsoleWidgets",
70
72
  "logger",
71
73
  "nodesReferenceBinding",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/realm-host-surface.generated.ts"],"sourcesContent":["/**\n * GENERATED FILE — do not edit. Regenerate with:\n * node tools/scripts/generate-realm-host-exports.mjs\n *\n * The names each realm host module holds (AGL-3392), for the bundle\n * verifier: a bundle reading any other name from the host is refused,\n * because it would read `undefined` on every site.\n */\n/* eslint-disable */\n\nexport const REALM_HOST_SURFACE_NAMES: Readonly<\n Record<'aglyn' | 'mui' | 'muiStyles', ReadonlySet<string>>\n> = {\n aglyn: new Set([\"Aglyn\",\"CATEGORY_REQUIRED_CAPABILITY\",\"COMPONENT_CATEGORY_ORDER\",\"CONSOLE_STAFF_WIDGET_SLOTS\",\"CONSOLE_WIDGET_SLOTS\",\"ComponentCategory\",\"EnabledPluginsContext\",\"FEATURE_FLAG\",\"FUNCTION_BUILTIN_NAMES\",\"FUNCTION_MAX_OPERATIONS\",\"FUNCTION_TOKEN_PATTERN\",\"FieldComponentType\",\"FieldValidatorType\",\"HOST_VARIABLE_TYPE_LABELS\",\"LinealDirectiveFlag\",\"MISSING_BINDING_LABEL\",\"MUI_BUNDLE_ID\",\"NAME_TOKEN_PATTERN\",\"NodeIdentityContext\",\"NodeType\",\"PREVIEW_REFUSED_STATUS\",\"PREVIEW_WRITE_BLOCKED_EVENT\",\"REUSABLE_COMPONENT_CATEGORY\",\"REUSABLE_EMAIL_BLOCK_CATEGORY\",\"RICH_TEXT_COMMANDS\",\"ScreenLinkContext\",\"SiteContext\",\"VARIABLE_ID_TOKEN_PATTERN\",\"VARIABLE_NAME_PATTERN\",\"aglyn\",\"attachFunctionDefinitions\",\"bindingTokenNeedsDeep\",\"canvas\",\"components\",\"defineUiFeatureBundle\",\"displayBindingTokens\",\"editableBindingTokens\",\"emitter\",\"evaluateExpression\",\"evaluateHostFunction\",\"expressionIdentifiers\",\"formatFunctionIdToken\",\"formatFunctionParameterOptions\",\"formatVariableIdToken\",\"formatVariableValue\",\"functionGlobals\",\"functionReferencedNames\",\"hasBindings\",\"isCategoryCapabilityEnabled\",\"isConsoleStaffWidgetSlot\",\"isFromEnabledPlugin\",\"isPreviewRefusal\",\"isSwitchedOffForRenderedSite\",\"keyById\",\"listConsoleExtensions\",\"listConsoleNavItems\",\"listConsoleOrgNavItems\",\"listConsoleProviders\",\"listConsoleStaffPages\",\"listConsoleWidgets\",\"logger\",\"nodesReferenceBinding\",\"normalizeBindingTokens\",\"parseFunctionParameterOptions\",\"plugins\",\"registerConsoleExtension\",\"resolveBindings\",\"resolveConsoleOrgPluginPage\",\"resolveConsolePluginPage\",\"resolveConsoleStaffPage\",\"resolveNodesBindings\",\"rewriteBindingTokensDeep\",\"textReferencesBinding\",\"unregisterConsoleExtension\",\"useEnabledPlugins\",\"useNodeId\",\"useSite\",\"useSiteFetch\"]),\n mui: new Set([\"Alert\",\"Box\",\"Button\",\"Card\",\"CardContent\",\"Checkbox\",\"Chip\",\"CircularProgress\",\"Collapse\",\"Divider\",\"FormControl\",\"FormControlLabel\",\"FormHelperText\",\"IconButton\",\"InputAdornment\",\"InputLabel\",\"Link\",\"List\",\"ListItem\",\"ListItemText\",\"MenuItem\",\"Paper\",\"Radio\",\"RadioGroup\",\"Select\",\"Stack\",\"SvgIcon\",\"Switch\",\"Table\",\"TableBody\",\"TableCell\",\"TableHead\",\"TableRow\",\"TextField\",\"Tooltip\",\"Typography\",\"createSvgIcon\",\"useMediaQuery\"]),\n muiStyles: new Set([\"alpha\",\"css\",\"darken\",\"emphasize\",\"getContrastRatio\",\"keyframes\",\"lighten\",\"styled\",\"useColorScheme\",\"useTheme\"]),\n}\n"],"names":["REALM_HOST_SURFACE_NAMES","aglyn","Set","mui","muiStyles"],"mappings":"AAAA;;;;;;;CAOC,GACD,kBAAkB,GAElB,OAAO,MAAMA,2BAET;IACFC,OAAO,IAAIC,IAAI;QAAC;QAAQ;QAA+B;QAA2B;QAA6B;QAAuB;QAAoB;QAAwB;QAAe;QAAyB;QAA0B;QAAyB;QAAqB;QAAqB;QAA4B;QAAsB;QAAwB;QAAgB;QAAqB;QAAsB;QAAW;QAAyB;QAA8B;QAA8B;QAAgC;QAAqB;QAAoB;QAAc;QAA4B;QAAwB;QAAQ;QAA4B;QAAwB;QAAS;QAAa;QAAwB;QAAuB;QAAwB;QAAU;QAAqB;QAAuB;QAAwB;QAAwB;QAAiC;QAAwB;QAAsB;QAAkB;QAA0B;QAAc;QAA8B;QAA2B;QAAsB;QAAmB;QAA+B;QAAU;QAAwB;QAAsB;QAAyB;QAAuB;QAAwB;QAAqB;QAAS;QAAwB;QAAyB;QAAgC;QAAU;QAA2B;QAAkB;QAA8B;QAA2B;QAA0B;QAAuB;QAA2B;QAAwB;QAA6B;QAAoB;QAAY;QAAU;KAAe;IACzsDC,KAAK,IAAID,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAO;QAAc;QAAW;QAAO;QAAmB;QAAW;QAAU;QAAc;QAAmB;QAAiB;QAAa;QAAiB;QAAa;QAAO;QAAO;QAAW;QAAe;QAAW;QAAQ;QAAQ;QAAa;QAAS;QAAQ;QAAU;QAAS;QAAQ;QAAY;QAAY;QAAY;QAAW;QAAY;QAAU;QAAa;QAAgB;KAAgB;IAC/bE,WAAW,IAAIF,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAY;QAAmB;QAAY;QAAU;QAAS;QAAiB;KAAW;AACvI,EAAC"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/realm-host-surface.generated.ts"],"sourcesContent":["/**\n * GENERATED FILE — do not edit. Regenerate with:\n * node tools/scripts/generate-realm-host-exports.mjs\n *\n * The names each realm host module holds (AGL-3392), for the bundle\n * verifier: a bundle reading any other name from the host is refused,\n * because it would read `undefined` on every site.\n */\n/* eslint-disable */\n\nexport const REALM_HOST_SURFACE_NAMES: Readonly<\n Record<'aglyn' | 'mui' | 'muiStyles', ReadonlySet<string>>\n> = {\n aglyn: new Set([\"Aglyn\",\"CATEGORY_REQUIRED_CAPABILITY\",\"COMPONENT_CATEGORY_ORDER\",\"CONSOLE_STAFF_WIDGET_SLOTS\",\"CONSOLE_WIDGET_SLOTS\",\"ComponentCategory\",\"EnabledPluginsContext\",\"FEATURE_FLAG\",\"FUNCTION_BUILTIN_NAMES\",\"FUNCTION_MAX_OPERATIONS\",\"FUNCTION_TOKEN_PATTERN\",\"FieldComponentType\",\"FieldValidatorType\",\"HOST_VARIABLE_TYPE_LABELS\",\"LinealDirectiveFlag\",\"MISSING_BINDING_LABEL\",\"MUI_BUNDLE_ID\",\"NAME_TOKEN_PATTERN\",\"NodeIdentityContext\",\"NodeType\",\"PREVIEW_REFUSED_STATUS\",\"PREVIEW_WRITE_BLOCKED_EVENT\",\"REUSABLE_COMPONENT_CATEGORY\",\"REUSABLE_EMAIL_BLOCK_CATEGORY\",\"RICH_TEXT_COMMANDS\",\"ScreenLinkContext\",\"SiteContext\",\"THEME_PRESETS_LOAD_POINT\",\"VARIABLE_ID_TOKEN_PATTERN\",\"VARIABLE_NAME_PATTERN\",\"aglyn\",\"attachFunctionDefinitions\",\"bindingTokenNeedsDeep\",\"canvas\",\"components\",\"defineUiFeatureBundle\",\"displayBindingTokens\",\"editableBindingTokens\",\"emitter\",\"evaluateExpression\",\"evaluateHostFunction\",\"expressionIdentifiers\",\"formatFunctionIdToken\",\"formatFunctionParameterOptions\",\"formatVariableIdToken\",\"formatVariableValue\",\"functionGlobals\",\"functionReferencedNames\",\"hasBindings\",\"isCategoryCapabilityEnabled\",\"isConsoleStaffWidgetSlot\",\"isFromEnabledPlugin\",\"isPreviewRefusal\",\"isSwitchedOffForRenderedSite\",\"keyById\",\"listConsoleExtensions\",\"listConsoleNavItems\",\"listConsoleOrgNavItems\",\"listConsoleProviders\",\"listConsoleStaffPages\",\"listConsoleThemePresets\",\"listConsoleWidgets\",\"logger\",\"nodesReferenceBinding\",\"normalizeBindingTokens\",\"parseFunctionParameterOptions\",\"plugins\",\"registerConsoleExtension\",\"resolveBindings\",\"resolveConsoleOrgPluginPage\",\"resolveConsolePluginPage\",\"resolveConsoleStaffPage\",\"resolveNodesBindings\",\"rewriteBindingTokensDeep\",\"textReferencesBinding\",\"unregisterConsoleExtension\",\"useEnabledPlugins\",\"useNodeId\",\"useSite\",\"useSiteFetch\"]),\n mui: new Set([\"Alert\",\"Box\",\"Button\",\"Card\",\"CardContent\",\"Checkbox\",\"Chip\",\"CircularProgress\",\"Collapse\",\"Divider\",\"FormControl\",\"FormControlLabel\",\"FormHelperText\",\"IconButton\",\"InputAdornment\",\"InputLabel\",\"Link\",\"List\",\"ListItem\",\"ListItemText\",\"MenuItem\",\"Paper\",\"Radio\",\"RadioGroup\",\"Select\",\"Stack\",\"SvgIcon\",\"Switch\",\"Table\",\"TableBody\",\"TableCell\",\"TableHead\",\"TableRow\",\"TextField\",\"Tooltip\",\"Typography\",\"createSvgIcon\",\"useMediaQuery\"]),\n muiStyles: new Set([\"alpha\",\"css\",\"darken\",\"emphasize\",\"getContrastRatio\",\"keyframes\",\"lighten\",\"styled\",\"useColorScheme\",\"useTheme\"]),\n}\n"],"names":["REALM_HOST_SURFACE_NAMES","aglyn","Set","mui","muiStyles"],"mappings":"AAAA;;;;;;;CAOC,GACD,kBAAkB,GAElB,OAAO,MAAMA,2BAET;IACFC,OAAO,IAAIC,IAAI;QAAC;QAAQ;QAA+B;QAA2B;QAA6B;QAAuB;QAAoB;QAAwB;QAAe;QAAyB;QAA0B;QAAyB;QAAqB;QAAqB;QAA4B;QAAsB;QAAwB;QAAgB;QAAqB;QAAsB;QAAW;QAAyB;QAA8B;QAA8B;QAAgC;QAAqB;QAAoB;QAAc;QAA2B;QAA4B;QAAwB;QAAQ;QAA4B;QAAwB;QAAS;QAAa;QAAwB;QAAuB;QAAwB;QAAU;QAAqB;QAAuB;QAAwB;QAAwB;QAAiC;QAAwB;QAAsB;QAAkB;QAA0B;QAAc;QAA8B;QAA2B;QAAsB;QAAmB;QAA+B;QAAU;QAAwB;QAAsB;QAAyB;QAAuB;QAAwB;QAA0B;QAAqB;QAAS;QAAwB;QAAyB;QAAgC;QAAU;QAA2B;QAAkB;QAA8B;QAA2B;QAA0B;QAAuB;QAA2B;QAAwB;QAA6B;QAAoB;QAAY;QAAU;KAAe;IAC9vDC,KAAK,IAAID,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAO;QAAc;QAAW;QAAO;QAAmB;QAAW;QAAU;QAAc;QAAmB;QAAiB;QAAa;QAAiB;QAAa;QAAO;QAAO;QAAW;QAAe;QAAW;QAAQ;QAAQ;QAAa;QAAS;QAAQ;QAAU;QAAS;QAAQ;QAAY;QAAY;QAAY;QAAW;QAAY;QAAU;QAAa;QAAgB;KAAgB;IAC/bE,WAAW,IAAIF,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAY;QAAmB;QAAY;QAAU;QAAS;QAAiB;KAAW;AACvI,EAAC"}