@aglyn/aglyn 1.0.0-beta.285 → 1.0.0-beta.288

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 (91) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-acquisition.d.ts +14 -0
  3. package/src/lib/app-utils/account-acquisition.js +19 -0
  4. package/src/lib/app-utils/account-acquisition.js.map +1 -1
  5. package/src/lib/app-utils/api-plugins.d.ts +25 -0
  6. package/src/lib/app-utils/api-plugins.js +13 -0
  7. package/src/lib/app-utils/api-plugins.js.map +1 -1
  8. package/src/lib/app-utils/business-profile-values.d.ts +15 -3
  9. package/src/lib/app-utils/business-profile-values.js +19 -3
  10. package/src/lib/app-utils/business-profile-values.js.map +1 -1
  11. package/src/lib/app-utils/business-profile.d.ts +5 -3
  12. package/src/lib/app-utils/business-profile.js +2 -1
  13. package/src/lib/app-utils/business-profile.js.map +1 -1
  14. package/src/lib/app-utils/contacts.d.ts +11 -0
  15. package/src/lib/app-utils/contacts.js +18 -0
  16. package/src/lib/app-utils/contacts.js.map +1 -1
  17. package/src/lib/app-utils/docs-deflection.d.ts +1 -1
  18. package/src/lib/app-utils/docs-deflection.js +17 -0
  19. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  20. package/src/lib/app-utils/docs-help-sections.generated.js +5 -2
  21. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  22. package/src/lib/app-utils/docs-help.generated.d.ts +9 -9
  23. package/src/lib/app-utils/docs-help.generated.js +20 -0
  24. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  25. package/src/lib/app-utils/docs-index.generated.js +168 -63
  26. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  27. package/src/lib/app-utils/docs-retrieval.d.ts +17 -0
  28. package/src/lib/app-utils/docs-retrieval.js +28 -0
  29. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  30. package/src/lib/app-utils/host-naming.d.ts +2 -1
  31. package/src/lib/app-utils/host-naming.js +65 -2
  32. package/src/lib/app-utils/host-naming.js.map +1 -1
  33. package/src/lib/app-utils/host-permissions.d.ts +159 -3
  34. package/src/lib/app-utils/host-permissions.js +384 -12
  35. package/src/lib/app-utils/host-permissions.js.map +1 -1
  36. package/src/lib/app-utils/org-permission-editor.d.ts +128 -1
  37. package/src/lib/app-utils/org-permission-editor.js +176 -1
  38. package/src/lib/app-utils/org-permission-editor.js.map +1 -1
  39. package/src/lib/app-utils/org-permissions.d.ts +62 -1
  40. package/src/lib/app-utils/org-permissions.js +55 -5
  41. package/src/lib/app-utils/org-permissions.js.map +1 -1
  42. package/src/lib/app-utils/org-role-grants.d.ts +28 -0
  43. package/src/lib/app-utils/org-role-grants.js +180 -2
  44. package/src/lib/app-utils/org-role-grants.js.map +1 -1
  45. package/src/lib/app-utils/org-roles.js +11 -2
  46. package/src/lib/app-utils/org-roles.js.map +1 -1
  47. package/src/lib/app-utils/redirect-query.d.ts +39 -0
  48. package/src/lib/app-utils/redirect-query.js +57 -0
  49. package/src/lib/app-utils/redirect-query.js.map +1 -0
  50. package/src/lib/app-utils/release-flags.js +2 -2
  51. package/src/lib/app-utils/release-flags.js.map +1 -1
  52. package/src/lib/app-utils/site-starter-links.d.ts +42 -0
  53. package/src/lib/app-utils/site-starter-links.js +79 -0
  54. package/src/lib/app-utils/site-starter-links.js.map +1 -0
  55. package/src/lib/app-utils/site-starter-nodes.d.ts +91 -0
  56. package/src/lib/app-utils/site-starter-nodes.js +210 -0
  57. package/src/lib/app-utils/site-starter-nodes.js.map +1 -0
  58. package/src/lib/app-utils/site-starter-theme.d.ts +90 -0
  59. package/src/lib/app-utils/site-starter-theme.js +241 -0
  60. package/src/lib/app-utils/site-starter-theme.js.map +1 -0
  61. package/src/lib/app-utils/webhook-delivery.js +6 -1
  62. package/src/lib/app-utils/webhook-delivery.js.map +1 -1
  63. package/src/lib/foundation/definitions/organization.types.d.ts +12 -2
  64. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  65. package/src/lib/plugin-manager/first-party-plugins.generated.js +39 -0
  66. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  67. package/src/lib/plugin-manager/plugin-closure-notices.d.ts +88 -0
  68. package/src/lib/plugin-manager/plugin-closure-notices.js +123 -0
  69. package/src/lib/plugin-manager/plugin-closure-notices.js.map +1 -0
  70. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +15 -0
  71. package/src/lib/plugin-manager/plugin-contact-capture.js +24 -1
  72. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  73. package/src/lib/plugin-manager/plugin-customer-display-verifier.d.ts +77 -0
  74. package/src/lib/plugin-manager/plugin-customer-display-verifier.js +74 -0
  75. package/src/lib/plugin-manager/plugin-customer-display-verifier.js.map +1 -0
  76. package/src/lib/plugin-manager/plugin-customer-display.d.ts +135 -0
  77. package/src/lib/plugin-manager/plugin-customer-display.js +189 -0
  78. package/src/lib/plugin-manager/plugin-customer-display.js.map +1 -0
  79. package/src/lib/plugin-manager/plugin-entitlements.d.ts +59 -0
  80. package/src/lib/plugin-manager/plugin-entitlements.js +32 -3
  81. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  82. package/src/lib/plugin-manager/plugin-events.d.ts +45 -0
  83. package/src/lib/plugin-manager/plugin-events.js.map +1 -1
  84. package/src/lib/plugin-manager/plugin-permissions.d.ts +74 -0
  85. package/src/lib/plugin-manager/plugin-permissions.js +91 -1
  86. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-site-starters.d.ts +7 -0
  88. package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -1
  89. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +87 -4
  90. package/src/lib/plugin-manager/plugin-tax-return-sources.js +155 -35
  91. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PortableDefinitionNodes } from './node-definition-sanitizer';
18
+ /**
19
+ * What a site starter's pages and layout may hold (AGL-4049), and the one
20
+ * pass that makes them so.
21
+ *
22
+ * A starter is a whole site, so it has to carry what a real site is made of:
23
+ * the grids, cards, accordions and icons the default site is built from and
24
+ * the AI site build lays out, with their styles and their interactions. The
25
+ * portable rule every other artifact is published under keeps none of that —
26
+ * it is for a component dropped into someone else's page — so a starter gets
27
+ * its own allowlist here, a superset of the portable one, and keeps `sx` and
28
+ * `interactions` once they are checked. Nothing else is widened: the template,
29
+ * layout and component doors still publish under the portable rule.
30
+ *
31
+ * Kept safe the same way, plus two checks of its own:
32
+ * - only the component ids below, nothing bound to the source site's data
33
+ * (content collections, datasets) or to another plugin;
34
+ * - the portable prop hardening (no `on*`, no `dangerouslySetInnerHTML`, an
35
+ * `href`/`src` only to a safe address) and the size cap;
36
+ * - a style may load an image only from where an image `src` may, and never
37
+ * from a protocol-relative address;
38
+ * - an interaction may only scroll, show, hide, open, close, toggle a class,
39
+ * stick the navigation or play a video: no script, markup, redirect or
40
+ * tracking rides a starter onto the sites created from it.
41
+ *
42
+ * The publish door runs every page and the layout through
43
+ * {@link sanitizeSiteStarterNodes}; the first-party seed holds its pages to the
44
+ * same pass, so a seeded and a published starter store one shape. Pure.
45
+ */
46
+ /**
47
+ * The component ids a starter's pages may hold: the portable allowlist, what
48
+ * `default-site.ts` builds, and the presentational elements the AI site build
49
+ * lays out (its palette's page surface, less anything bound to a site's data).
50
+ */
51
+ export declare const SITE_STARTER_COMPONENT_IDS: readonly string[];
52
+ /** The node marking where a page's content grafts into its layout. */
53
+ export declare const SITE_STARTER_LAYOUT_SLOT_ID = "layoutSlot";
54
+ /** A starter's layout may hold the same, plus the slot its pages render in. */
55
+ export declare const SITE_STARTER_LAYOUT_COMPONENT_IDS: readonly string[];
56
+ /**
57
+ * The interaction steps a starter may carry: each acts on the page it is on
58
+ * and nothing else. Scripts, markup, redirects, attributes, alerts, popups and
59
+ * analytics are left out, as is every step a plugin declares.
60
+ */
61
+ export declare const SITE_STARTER_INTERACTION_STEP_TYPES: ReadonlySet<string>;
62
+ /** A starter's node as stored: a portable node, with its checked styles and interactions. */
63
+ export type SiteStarterNodes = Record<string, PortableDefinitionNodes[string] & {
64
+ sx?: unknown;
65
+ interactions?: unknown[];
66
+ }>;
67
+ /**
68
+ * A starter page's or layout's nodes as a starter stores them, or why they
69
+ * cannot be: the portable pass under {@link SITE_STARTER_COMPONENT_IDS} (or
70
+ * the layout list), then each kept node's checked `sx` and `interactions`
71
+ * put back. Callers decode stored nodes first (`decodeStoredNodes`).
72
+ */
73
+ export declare function sanitizeSiteStarterNodes(definition: {
74
+ rootId: string;
75
+ nodes: Record<string, any>;
76
+ }, options?: {
77
+ /** A layout: the slot its pages render in is allowed too. */
78
+ layout?: boolean;
79
+ /** The properties a layout's tree binds to, as `sanitizePortableDefinition` takes them. */
80
+ declaredProps?: ReadonlyArray<{
81
+ name?: unknown;
82
+ type?: unknown;
83
+ }>;
84
+ }): {
85
+ ok: true;
86
+ rootId: string;
87
+ nodes: SiteStarterNodes;
88
+ } | {
89
+ ok: false;
90
+ error: string;
91
+ };
@@ -0,0 +1,210 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { NODE_MAX_INTERACTIONS } from "./node-interactions.js";
18
+ import { PORTABLE_COMPONENT_ID_ALLOWLIST, PORTABLE_DEFINITION_MAX_BYTES, sanitizePortableDefinition } from "./node-definition-sanitizer.js";
19
+ import { isSafeNodeUrl } from "./node-url-policy.js";
20
+ import { CLIENT_INTERACTION_STEP_LABELS } from "./site-interactions.js";
21
+ /**
22
+ * What a site starter's pages and layout may hold (AGL-4049), and the one
23
+ * pass that makes them so.
24
+ *
25
+ * A starter is a whole site, so it has to carry what a real site is made of:
26
+ * the grids, cards, accordions and icons the default site is built from and
27
+ * the AI site build lays out, with their styles and their interactions. The
28
+ * portable rule every other artifact is published under keeps none of that —
29
+ * it is for a component dropped into someone else's page — so a starter gets
30
+ * its own allowlist here, a superset of the portable one, and keeps `sx` and
31
+ * `interactions` once they are checked. Nothing else is widened: the template,
32
+ * layout and component doors still publish under the portable rule.
33
+ *
34
+ * Kept safe the same way, plus two checks of its own:
35
+ * - only the component ids below, nothing bound to the source site's data
36
+ * (content collections, datasets) or to another plugin;
37
+ * - the portable prop hardening (no `on*`, no `dangerouslySetInnerHTML`, an
38
+ * `href`/`src` only to a safe address) and the size cap;
39
+ * - a style may load an image only from where an image `src` may, and never
40
+ * from a protocol-relative address;
41
+ * - an interaction may only scroll, show, hide, open, close, toggle a class,
42
+ * stick the navigation or play a video: no script, markup, redirect or
43
+ * tracking rides a starter onto the sites created from it.
44
+ *
45
+ * The publish door runs every page and the layout through
46
+ * {@link sanitizeSiteStarterNodes}; the first-party seed holds its pages to the
47
+ * same pass, so a seeded and a published starter store one shape. Pure.
48
+ */ /**
49
+ * The component ids a starter's pages may hold: the portable allowlist, what
50
+ * `default-site.ts` builds, and the presentational elements the AI site build
51
+ * lays out (its palette's page surface, less anything bound to a site's data).
52
+ */ export const SITE_STARTER_COMPONENT_IDS = [
53
+ ...new Set([
54
+ ...PORTABLE_COMPONENT_ID_ALLOWLIST,
55
+ // The default site (`default-site.ts`).
56
+ 'icon',
57
+ 'muiAccordion',
58
+ 'muiAccordionDetails',
59
+ 'muiAccordionSummary',
60
+ 'muiCard',
61
+ 'muiCardContent',
62
+ 'muiGrid',
63
+ 'themeModeSwitcher',
64
+ // The AI site build's presentational elements.
65
+ 'dataTable',
66
+ 'muiBox',
67
+ 'muiBreadcrumbs',
68
+ 'muiCardActions',
69
+ 'muiCardHeader',
70
+ 'muiImageList',
71
+ 'muiImageListItem',
72
+ 'video'
73
+ ])
74
+ ].sort();
75
+ /** The node marking where a page's content grafts into its layout. */ export const SITE_STARTER_LAYOUT_SLOT_ID = 'layoutSlot';
76
+ /** A starter's layout may hold the same, plus the slot its pages render in. */ export const SITE_STARTER_LAYOUT_COMPONENT_IDS = [
77
+ ...SITE_STARTER_COMPONENT_IDS,
78
+ SITE_STARTER_LAYOUT_SLOT_ID
79
+ ].sort();
80
+ /**
81
+ * The interaction steps a starter may carry: each acts on the page it is on
82
+ * and nothing else. Scripts, markup, redirects, attributes, alerts, popups and
83
+ * analytics are left out, as is every step a plugin declares.
84
+ */ export const SITE_STARTER_INTERACTION_STEP_TYPES = new Set([
85
+ 'addClass',
86
+ 'closeDrawer',
87
+ 'closeMenu',
88
+ 'hideElement',
89
+ 'openDrawer',
90
+ 'openMenu',
91
+ 'playVideo',
92
+ 'removeClass',
93
+ 'scrollTo',
94
+ 'showElement',
95
+ 'stickyNav',
96
+ 'toggleClass',
97
+ 'toggleDrawer',
98
+ 'toggleElement',
99
+ 'toggleMenu'
100
+ ]);
101
+ const isRecord = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
102
+ /** How a refusal names a node: its component, then its id. */ const nodeName = (id, node)=>{
103
+ var _ref;
104
+ return `the ${String((_ref = node == null ? void 0 : node.componentId) != null ? _ref : 'element')} "${id}"`;
105
+ };
106
+ const stepLabel = (type)=>{
107
+ var _CLIENT_INTERACTION_STEP_LABELS_type;
108
+ return (_CLIENT_INTERACTION_STEP_LABELS_type = CLIENT_INTERACTION_STEP_LABELS[type]) != null ? _CLIENT_INTERACTION_STEP_LABELS_type : type;
109
+ };
110
+ /** Every `url(…)` address in a style value. */ const styleUrls = (value)=>[
111
+ ...value.matchAll(/url\(\s*(['"]?)(.*?)\1\s*\)/gi)
112
+ ].map((match)=>match[2].trim());
113
+ /** Why a node's styles cannot ride a starter, or null when they can. */ function styleRefusal(id, node, sx, depth = 0) {
114
+ if (depth > 12) return `The styles on ${nodeName(id, node)} are nested too deeply`;
115
+ if (typeof sx === 'string') {
116
+ if (/url\(/i.test(sx) && !styleUrls(sx).length) {
117
+ return `A style on ${nodeName(id, node)} loads an address a starter cannot read`;
118
+ }
119
+ for (const address of styleUrls(sx)){
120
+ if (address.startsWith('//') || !isSafeNodeUrl('src', address)) {
121
+ return `A style on ${nodeName(id, node)} loads "${address.slice(0, 120)}", which a starter cannot carry`;
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ if (sx === null || typeof sx === 'number' || typeof sx === 'boolean') return null;
127
+ if (Array.isArray(sx)) {
128
+ for (const entry of sx){
129
+ const refusal = styleRefusal(id, node, entry, depth + 1);
130
+ if (refusal) return refusal;
131
+ }
132
+ return null;
133
+ }
134
+ if (isRecord(sx)) {
135
+ for (const entry of Object.values(sx)){
136
+ const refusal = styleRefusal(id, node, entry, depth + 1);
137
+ if (refusal) return refusal;
138
+ }
139
+ return null;
140
+ }
141
+ return `The styles on ${nodeName(id, node)} hold something that is not a style`;
142
+ }
143
+ /** Why a node's interactions cannot ride a starter, or null when they can. */ function interactionsRefusal(id, node, interactions) {
144
+ if (!Array.isArray(interactions)) return `The interactions on ${nodeName(id, node)} are not a list`;
145
+ if (interactions.length > NODE_MAX_INTERACTIONS) {
146
+ return `${nodeName(id, node)} has more than ${NODE_MAX_INTERACTIONS} interactions`;
147
+ }
148
+ for (const interaction of interactions){
149
+ if (!isRecord(interaction) || !Array.isArray(interaction['steps'])) {
150
+ return `An interaction on ${nodeName(id, node)} is not one`;
151
+ }
152
+ for (const step of interaction['steps']){
153
+ const type = isRecord(step) && typeof step['type'] === 'string' ? step['type'] : '';
154
+ if (!SITE_STARTER_INTERACTION_STEP_TYPES.has(type)) {
155
+ return `An interaction on ${nodeName(id, node)} runs "${stepLabel(type || 'an unknown step')}", ` + 'which a starter cannot carry: a starter brings no scripts, markup, redirects or ' + 'tracking to the sites created from it';
156
+ }
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ /** A JSON copy, so a stored starter never shares an object with its source. */ const copied = (value)=>JSON.parse(JSON.stringify(value));
162
+ /**
163
+ * A starter page's or layout's nodes as a starter stores them, or why they
164
+ * cannot be: the portable pass under {@link SITE_STARTER_COMPONENT_IDS} (or
165
+ * the layout list), then each kept node's checked `sx` and `interactions`
166
+ * put back. Callers decode stored nodes first (`decodeStoredNodes`).
167
+ */ export function sanitizeSiteStarterNodes(definition, options = {}) {
168
+ const portable = sanitizePortableDefinition(definition, _extends({
169
+ componentIds: options.layout ? SITE_STARTER_LAYOUT_COMPONENT_IDS : SITE_STARTER_COMPONENT_IDS
170
+ }, options.declaredProps && {
171
+ declaredProps: options.declaredProps
172
+ }));
173
+ if (portable.ok === false) return portable;
174
+ const nodes = {};
175
+ for (const [id, kept] of Object.entries(portable.nodes)){
176
+ var _definition_nodes_id;
177
+ const source = (_definition_nodes_id = definition.nodes[id]) != null ? _definition_nodes_id : {};
178
+ const node = _extends({}, kept);
179
+ if (source.sx !== undefined && source.sx !== null) {
180
+ const refusal = styleRefusal(id, source, source.sx);
181
+ if (refusal) return {
182
+ ok: false,
183
+ error: refusal
184
+ };
185
+ node['sx'] = copied(source.sx);
186
+ }
187
+ if (source.interactions !== undefined && source.interactions !== null) {
188
+ const refusal = interactionsRefusal(id, source, source.interactions);
189
+ if (refusal) return {
190
+ ok: false,
191
+ error: refusal
192
+ };
193
+ if (source.interactions.length) node['interactions'] = copied(source.interactions);
194
+ }
195
+ nodes[id] = node;
196
+ }
197
+ if (JSON.stringify(nodes).length > PORTABLE_DEFINITION_MAX_BYTES) {
198
+ return {
199
+ ok: false,
200
+ error: 'Definition is too large to publish'
201
+ };
202
+ }
203
+ return {
204
+ ok: true,
205
+ rootId: portable.rootId,
206
+ nodes
207
+ };
208
+ }
209
+
210
+ //# sourceMappingURL=site-starter-nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-starter-nodes.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 { NODE_MAX_INTERACTIONS } from './node-interactions'\nimport {\n PORTABLE_COMPONENT_ID_ALLOWLIST,\n PORTABLE_DEFINITION_MAX_BYTES,\n sanitizePortableDefinition,\n type PortableDefinitionNodes,\n} from './node-definition-sanitizer'\nimport { isSafeNodeUrl } from './node-url-policy'\nimport { CLIENT_INTERACTION_STEP_LABELS } from './site-interactions'\n\n/**\n * What a site starter's pages and layout may hold (AGL-4049), and the one\n * pass that makes them so.\n *\n * A starter is a whole site, so it has to carry what a real site is made of:\n * the grids, cards, accordions and icons the default site is built from and\n * the AI site build lays out, with their styles and their interactions. The\n * portable rule every other artifact is published under keeps none of that —\n * it is for a component dropped into someone else's page — so a starter gets\n * its own allowlist here, a superset of the portable one, and keeps `sx` and\n * `interactions` once they are checked. Nothing else is widened: the template,\n * layout and component doors still publish under the portable rule.\n *\n * Kept safe the same way, plus two checks of its own:\n * - only the component ids below, nothing bound to the source site's data\n * (content collections, datasets) or to another plugin;\n * - the portable prop hardening (no `on*`, no `dangerouslySetInnerHTML`, an\n * `href`/`src` only to a safe address) and the size cap;\n * - a style may load an image only from where an image `src` may, and never\n * from a protocol-relative address;\n * - an interaction may only scroll, show, hide, open, close, toggle a class,\n * stick the navigation or play a video: no script, markup, redirect or\n * tracking rides a starter onto the sites created from it.\n *\n * The publish door runs every page and the layout through\n * {@link sanitizeSiteStarterNodes}; the first-party seed holds its pages to the\n * same pass, so a seeded and a published starter store one shape. Pure.\n */\n\n/**\n * The component ids a starter's pages may hold: the portable allowlist, what\n * `default-site.ts` builds, and the presentational elements the AI site build\n * lays out (its palette's page surface, less anything bound to a site's data).\n */\nexport const SITE_STARTER_COMPONENT_IDS: readonly string[] = [\n ...new Set([\n ...PORTABLE_COMPONENT_ID_ALLOWLIST,\n // The default site (`default-site.ts`).\n 'icon',\n 'muiAccordion',\n 'muiAccordionDetails',\n 'muiAccordionSummary',\n 'muiCard',\n 'muiCardContent',\n 'muiGrid',\n 'themeModeSwitcher',\n // The AI site build's presentational elements.\n 'dataTable',\n 'muiBox',\n 'muiBreadcrumbs',\n 'muiCardActions',\n 'muiCardHeader',\n 'muiImageList',\n 'muiImageListItem',\n 'video',\n ]),\n].sort()\n\n/** The node marking where a page's content grafts into its layout. */\nexport const SITE_STARTER_LAYOUT_SLOT_ID = 'layoutSlot'\n\n/** A starter's layout may hold the same, plus the slot its pages render in. */\nexport const SITE_STARTER_LAYOUT_COMPONENT_IDS: readonly string[] = [\n ...SITE_STARTER_COMPONENT_IDS,\n SITE_STARTER_LAYOUT_SLOT_ID,\n].sort()\n\n/**\n * The interaction steps a starter may carry: each acts on the page it is on\n * and nothing else. Scripts, markup, redirects, attributes, alerts, popups and\n * analytics are left out, as is every step a plugin declares.\n */\nexport const SITE_STARTER_INTERACTION_STEP_TYPES: ReadonlySet<string> = new Set([\n 'addClass',\n 'closeDrawer',\n 'closeMenu',\n 'hideElement',\n 'openDrawer',\n 'openMenu',\n 'playVideo',\n 'removeClass',\n 'scrollTo',\n 'showElement',\n 'stickyNav',\n 'toggleClass',\n 'toggleDrawer',\n 'toggleElement',\n 'toggleMenu',\n])\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object' && !Array.isArray(value)\n\n/** How a refusal names a node: its component, then its id. */\nconst nodeName = (id: string, node: Record<string, any>) =>\n `the ${String(node?.componentId ?? 'element')} \"${id}\"`\n\nconst stepLabel = (type: string): string =>\n (CLIENT_INTERACTION_STEP_LABELS as Readonly<Record<string, string>>)[type] ?? type\n\n/** Every `url(…)` address in a style value. */\nconst styleUrls = (value: string): string[] =>\n [...value.matchAll(/url\\(\\s*(['\"]?)(.*?)\\1\\s*\\)/gi)].map((match) => match[2].trim())\n\n/** Why a node's styles cannot ride a starter, or null when they can. */\nfunction styleRefusal(id: string, node: Record<string, any>, sx: unknown, depth = 0): string | null {\n if (depth > 12) return `The styles on ${nodeName(id, node)} are nested too deeply`\n if (typeof sx === 'string') {\n if (/url\\(/i.test(sx) && !styleUrls(sx).length) {\n return `A style on ${nodeName(id, node)} loads an address a starter cannot read`\n }\n for (const address of styleUrls(sx)) {\n if (address.startsWith('//') || !isSafeNodeUrl('src', address)) {\n return `A style on ${nodeName(id, node)} loads \"${address.slice(0, 120)}\", which a starter cannot carry`\n }\n }\n return null\n }\n if (sx === null || typeof sx === 'number' || typeof sx === 'boolean') return null\n if (Array.isArray(sx)) {\n for (const entry of sx) {\n const refusal = styleRefusal(id, node, entry, depth + 1)\n if (refusal) return refusal\n }\n return null\n }\n if (isRecord(sx)) {\n for (const entry of Object.values(sx)) {\n const refusal = styleRefusal(id, node, entry, depth + 1)\n if (refusal) return refusal\n }\n return null\n }\n return `The styles on ${nodeName(id, node)} hold something that is not a style`\n}\n\n/** Why a node's interactions cannot ride a starter, or null when they can. */\nfunction interactionsRefusal(id: string, node: Record<string, any>, interactions: unknown): string | null {\n if (!Array.isArray(interactions)) return `The interactions on ${nodeName(id, node)} are not a list`\n if (interactions.length > NODE_MAX_INTERACTIONS) {\n return `${nodeName(id, node)} has more than ${NODE_MAX_INTERACTIONS} interactions`\n }\n for (const interaction of interactions) {\n if (!isRecord(interaction) || !Array.isArray(interaction['steps'])) {\n return `An interaction on ${nodeName(id, node)} is not one`\n }\n for (const step of interaction['steps'] as unknown[]) {\n const type = isRecord(step) && typeof step['type'] === 'string' ? step['type'] : ''\n if (!SITE_STARTER_INTERACTION_STEP_TYPES.has(type)) {\n return (\n `An interaction on ${nodeName(id, node)} runs \"${stepLabel(type || 'an unknown step')}\", ` +\n 'which a starter cannot carry: a starter brings no scripts, markup, redirects or ' +\n 'tracking to the sites created from it'\n )\n }\n }\n }\n return null\n}\n\n/** A starter's node as stored: a portable node, with its checked styles and interactions. */\nexport type SiteStarterNodes = Record<\n string,\n PortableDefinitionNodes[string] & { sx?: unknown; interactions?: unknown[] }\n>\n\n/** A JSON copy, so a stored starter never shares an object with its source. */\nconst copied = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T\n\n/**\n * A starter page's or layout's nodes as a starter stores them, or why they\n * cannot be: the portable pass under {@link SITE_STARTER_COMPONENT_IDS} (or\n * the layout list), then each kept node's checked `sx` and `interactions`\n * put back. Callers decode stored nodes first (`decodeStoredNodes`).\n */\nexport function sanitizeSiteStarterNodes(\n definition: { rootId: string; nodes: Record<string, any> },\n options: {\n /** A layout: the slot its pages render in is allowed too. */\n layout?: boolean\n /** The properties a layout's tree binds to, as `sanitizePortableDefinition` takes them. */\n declaredProps?: ReadonlyArray<{ name?: unknown; type?: unknown }>\n } = {},\n): { ok: true; rootId: string; nodes: SiteStarterNodes } | { ok: false; error: string } {\n const portable = sanitizePortableDefinition(definition, {\n componentIds: options.layout ? SITE_STARTER_LAYOUT_COMPONENT_IDS : SITE_STARTER_COMPONENT_IDS,\n ...(options.declaredProps && { declaredProps: options.declaredProps }),\n })\n if (portable.ok === false) return portable\n const nodes: SiteStarterNodes = {}\n for (const [id, kept] of Object.entries(portable.nodes)) {\n const source = definition.nodes[id] ?? {}\n const node: SiteStarterNodes[string] = { ...kept }\n if (source.sx !== undefined && source.sx !== null) {\n const refusal = styleRefusal(id, source, source.sx)\n if (refusal) return { ok: false, error: refusal }\n node['sx'] = copied(source.sx)\n }\n if (source.interactions !== undefined && source.interactions !== null) {\n const refusal = interactionsRefusal(id, source, source.interactions)\n if (refusal) return { ok: false, error: refusal }\n if ((source.interactions as unknown[]).length) node['interactions'] = copied(source.interactions)\n }\n nodes[id] = node\n }\n if (JSON.stringify(nodes).length > PORTABLE_DEFINITION_MAX_BYTES) {\n return { ok: false, error: 'Definition is too large to publish' }\n }\n return { ok: true, rootId: portable.rootId, nodes }\n}\n"],"names":["NODE_MAX_INTERACTIONS","PORTABLE_COMPONENT_ID_ALLOWLIST","PORTABLE_DEFINITION_MAX_BYTES","sanitizePortableDefinition","isSafeNodeUrl","CLIENT_INTERACTION_STEP_LABELS","SITE_STARTER_COMPONENT_IDS","Set","sort","SITE_STARTER_LAYOUT_SLOT_ID","SITE_STARTER_LAYOUT_COMPONENT_IDS","SITE_STARTER_INTERACTION_STEP_TYPES","isRecord","value","Array","isArray","nodeName","id","node","String","componentId","stepLabel","type","styleUrls","matchAll","map","match","trim","styleRefusal","sx","depth","test","length","address","startsWith","slice","entry","refusal","Object","values","interactionsRefusal","interactions","interaction","step","has","copied","JSON","parse","stringify","sanitizeSiteStarterNodes","definition","options","portable","componentIds","layout","declaredProps","ok","nodes","kept","entries","source","undefined","error","rootId"],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { HostTheme } from '@aglyn/shared-data-types';
18
+ import type { ConsoleThemePreset } from '../plugin-manager/feature-plugins';
19
+ import type { SiteStarterTheme } from './site-starter';
20
+ import { type ThemeLibraryEntryWrite } from './theme-library';
21
+ /**
22
+ * A site starter's theme on the site it creates (AGL-4049).
23
+ *
24
+ * A starter names its look one of two ways (`SiteStarterTheme`): a built-in
25
+ * theme with its brand colors and font pairing over it, or a full theme of
26
+ * its author's own. Either way the new site's theme is stored the way a
27
+ * person's own pick is: the built-in picked through the theme library with
28
+ * the starter's colors and fonts as the override over it, or the full theme
29
+ * installed into the library as a marketplace theme is. So the Theme page
30
+ * names the base a site came from, and every edit there starts from it.
31
+ *
32
+ * Planned against an EMPTY host, because the site does not exist yet: the
33
+ * create writes these fields in the same `set` that makes the host, and the
34
+ * library entries beside it, in one transaction.
35
+ *
36
+ * Pure: no I/O. The caller passes the built-in themes it has registered.
37
+ */
38
+ /**
39
+ * The palette keys a built-in starter's brand sets: its three brand colors.
40
+ * The status colors (error, warning, info, success) stay the base theme's,
41
+ * since they mean the same thing on every site, and surfaces and text are the
42
+ * base's design.
43
+ */
44
+ export declare const SITE_STARTER_BRAND_KEYS: readonly ["primary", "secondary", "tertiary"];
45
+ /** The built-in theme a starter's `base` names, as the themes plugin registers it. */
46
+ export declare const siteStarterPresetId: (base: string) => string;
47
+ type BuiltinStarterTheme = Extract<SiteStarterTheme, {
48
+ kind: 'builtin';
49
+ }>;
50
+ /**
51
+ * A built-in base with the starter's brand colors and font pairing over it,
52
+ * modeled on the AI start's look (`aiSiteTheme`) but driven by the starter's
53
+ * `brand` and `fonts` rather than a style. Everything the base sets beyond
54
+ * those stays the base's: its status colors, surfaces, shapes and component
55
+ * styles. A brand with no `dark` map colors both schemes with `light`; a
56
+ * `fonts` of `null`, or a pairing id the platform does not have, keeps the
57
+ * base's own fonts.
58
+ */
59
+ export declare function siteStarterTheme(theme: BuiltinStarterTheme, base: HostTheme): HostTheme;
60
+ /** What a new site's host document and theme library start with. */
61
+ export interface SiteStarterThemePlan {
62
+ /** Host fields, written in the `set` that creates the host. */
63
+ host: Record<string, unknown>;
64
+ /** Library entries to create under `hosts/{id}/themes`. */
65
+ entries: ThemeLibraryEntryWrite[];
66
+ }
67
+ /**
68
+ * The host fields and library entries a new site is born with for a
69
+ * starter's theme, planned against an empty host through the same planner
70
+ * the theme library runs (`planThemeLibraryAction`):
71
+ *
72
+ * - a full theme is installed, as the marketplace installs a theme, its
73
+ * entry named after the starter and its provenance the starter's listing;
74
+ * - a built-in theme is picked as a preset, with the brand and fonts as the
75
+ * override over it;
76
+ * - the `starter` base, or a base this deployment has no built-in for, is
77
+ * the theme every new site is born with (`DEFAULT_SITE_THEME`), with the
78
+ * brand and fonts as the override over it, and no library entry.
79
+ */
80
+ export declare function planSiteStarterTheme(input: {
81
+ theme: SiteStarterTheme;
82
+ presets: readonly ConsoleThemePreset[];
83
+ listing: {
84
+ listingId: string;
85
+ version: string | number;
86
+ displayName: string;
87
+ };
88
+ newId: () => string;
89
+ }): SiteStarterThemePlan;
90
+ export {};
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { writeThemeFonts } from "@aglyn/shared-util-tools/theme-fonts";
18
+ import { diffOverride, overrideWriteValue } from "./artifact-overrides.js";
19
+ import { DEFAULT_SITE_THEME } from "./default-site.js";
20
+ import { siteFontPairing } from "./site-font-pairings.js";
21
+ import { themeArtifactContent } from "./site-theme.js";
22
+ import { planThemeLibraryAction, THEME_FIELD_DELETE } from "./theme-library.js";
23
+ /**
24
+ * A site starter's theme on the site it creates (AGL-4049).
25
+ *
26
+ * A starter names its look one of two ways (`SiteStarterTheme`): a built-in
27
+ * theme with its brand colors and font pairing over it, or a full theme of
28
+ * its author's own. Either way the new site's theme is stored the way a
29
+ * person's own pick is: the built-in picked through the theme library with
30
+ * the starter's colors and fonts as the override over it, or the full theme
31
+ * installed into the library as a marketplace theme is. So the Theme page
32
+ * names the base a site came from, and every edit there starts from it.
33
+ *
34
+ * Planned against an EMPTY host, because the site does not exist yet: the
35
+ * create writes these fields in the same `set` that makes the host, and the
36
+ * library entries beside it, in one transaction.
37
+ *
38
+ * Pure: no I/O. The caller passes the built-in themes it has registered.
39
+ */ /**
40
+ * The palette keys a built-in starter's brand sets: its three brand colors.
41
+ * The status colors (error, warning, info, success) stay the base theme's,
42
+ * since they mean the same thing on every site, and surfaces and text are the
43
+ * base's design.
44
+ */ export const SITE_STARTER_BRAND_KEYS = [
45
+ 'primary',
46
+ 'secondary',
47
+ 'tertiary'
48
+ ];
49
+ /** The built-in theme a starter's `base` names, as the themes plugin registers it. */ export const siteStarterPresetId = (base)=>`theme-presets.${base}`;
50
+ const isRecord = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
51
+ /**
52
+ * One scheme's colors with the brand over them, each brand color written the
53
+ * way the theme editor writes a palette color (`writeThemeColor`): a record of
54
+ * `main` alone, so MUI derives its light, dark and contrast text from it, and
55
+ * the base's tint for that color dropped, since it was a wash of the old one.
56
+ */ function brandScheme(colors, brand) {
57
+ let next = colors;
58
+ for (const key of SITE_STARTER_BRAND_KEYS){
59
+ const hex = brand == null ? void 0 : brand[key];
60
+ if (typeof hex !== 'string' || !hex.trim()) continue;
61
+ next = _extends({}, next, {
62
+ [key]: {
63
+ main: hex.trim()
64
+ }
65
+ });
66
+ const tint = next.tint;
67
+ if (tint && key in tint) {
68
+ const rest = _extends({}, tint);
69
+ delete rest[key];
70
+ if (Object.keys(rest).length) next.tint = rest;
71
+ else delete next.tint;
72
+ }
73
+ }
74
+ return next;
75
+ }
76
+ /**
77
+ * A built-in base with the starter's brand colors and font pairing over it,
78
+ * modeled on the AI start's look (`aiSiteTheme`) but driven by the starter's
79
+ * `brand` and `fonts` rather than a style. Everything the base sets beyond
80
+ * those stays the base's: its status colors, surfaces, shapes and component
81
+ * styles. A brand with no `dark` map colors both schemes with `light`; a
82
+ * `fonts` of `null`, or a pairing id the platform does not have, keeps the
83
+ * base's own fonts.
84
+ */ export function siteStarterTheme(theme, base) {
85
+ var _next_fonts;
86
+ const brand = isRecord(theme == null ? void 0 : theme.brand) ? theme.brand : {
87
+ light: {}
88
+ };
89
+ const light = isRecord(brand.light) ? brand.light : {};
90
+ const dark = isRecord(brand.dark) ? brand.dark : light;
91
+ const brandFor = {
92
+ light,
93
+ dark
94
+ };
95
+ const colorSchemes = _extends({}, base.colorSchemes);
96
+ for (const scheme of [
97
+ 'light',
98
+ 'dark'
99
+ ]){
100
+ var _base_colorSchemes;
101
+ const colors = brandScheme((_base_colorSchemes = base.colorSchemes) == null ? void 0 : _base_colorSchemes[scheme], brandFor[scheme]);
102
+ if (colors) colorSchemes[scheme] = colors;
103
+ }
104
+ let next = _extends({}, base);
105
+ if (Object.keys(colorSchemes).length) next.colorSchemes = colorSchemes;
106
+ const pairing = siteFontPairing(theme == null ? void 0 : theme.fonts);
107
+ if (!pairing) return next;
108
+ // The same write the AI site look makes for a pairing: the body family,
109
+ // and the heading family where it differs.
110
+ const sameFamily = pairing.heading.family === pairing.body.family;
111
+ next = writeThemeFonts(next, {
112
+ body: {
113
+ family: pairing.body.family,
114
+ category: pairing.body.category,
115
+ weights: [
116
+ ...pairing.body.weights
117
+ ],
118
+ source: 'google'
119
+ },
120
+ heading: sameFamily ? null : {
121
+ family: pairing.heading.family,
122
+ category: pairing.heading.category,
123
+ weights: [
124
+ ...new Set([
125
+ pairing.heading.weight,
126
+ pairing.heading.sub
127
+ ])
128
+ ].sort((a, b)=>a - b),
129
+ source: 'google'
130
+ }
131
+ });
132
+ // A pairing whose heading is the body family loads the heading weights too.
133
+ if (sameFamily && ((_next_fonts = next.fonts) == null ? void 0 : _next_fonts[0])) {
134
+ var _next_fonts__weights;
135
+ next.fonts = [
136
+ _extends({}, next.fonts[0], {
137
+ weights: [
138
+ ...new Set([
139
+ ...(_next_fonts__weights = next.fonts[0].weights) != null ? _next_fonts__weights : [],
140
+ pairing.heading.weight,
141
+ pairing.heading.sub
142
+ ])
143
+ ].sort((a, b)=>a - b)
144
+ }),
145
+ ...next.fonts.slice(1)
146
+ ];
147
+ }
148
+ return next;
149
+ }
150
+ /**
151
+ * A plan's host fields as a new document takes them: a field the plan
152
+ * deletes is simply not written, since an empty host has nothing to delete,
153
+ * and an empty override is no field at all.
154
+ */ function creatable(fields) {
155
+ return Object.fromEntries(Object.entries(fields).filter(([, value])=>value !== THEME_FIELD_DELETE && value !== null));
156
+ }
157
+ /** The override a built-in starter stores over `base`, or `null` when it changes nothing. */ function starterOverride(theme, base) {
158
+ return overrideWriteValue(diffOverride(base, siteStarterTheme(theme, base)), null);
159
+ }
160
+ /**
161
+ * The host fields and library entries a new site is born with for a
162
+ * starter's theme, planned against an empty host through the same planner
163
+ * the theme library runs (`planThemeLibraryAction`):
164
+ *
165
+ * - a full theme is installed, as the marketplace installs a theme, its
166
+ * entry named after the starter and its provenance the starter's listing;
167
+ * - a built-in theme is picked as a preset, with the brand and fonts as the
168
+ * override over it;
169
+ * - the `starter` base, or a base this deployment has no built-in for, is
170
+ * the theme every new site is born with (`DEFAULT_SITE_THEME`), with the
171
+ * brand and fonts as the override over it, and no library entry.
172
+ */ export function planSiteStarterTheme(input) {
173
+ var _presets_find;
174
+ // `source`, not `listing`: this is the starter's id/version/name, not the
175
+ // listing document the listing write-deny guard partitions (AGL-1361).
176
+ const { theme, presets, listing: source, newId } = input;
177
+ const plan = (action)=>{
178
+ const planned = planThemeLibraryAction({
179
+ host: {},
180
+ entries: {},
181
+ newId,
182
+ action
183
+ });
184
+ return planned.ok === false ? null : planned;
185
+ };
186
+ if ((theme == null ? void 0 : theme.kind) === 'full' && isRecord(theme.theme)) {
187
+ const planned = plan({
188
+ action: 'install',
189
+ listingId: source.listingId,
190
+ name: `${source.displayName} theme`,
191
+ theme: themeArtifactContent(theme.theme),
192
+ installedFrom: {
193
+ listingId: source.listingId,
194
+ version: source.version,
195
+ artifactType: 'siteStarter'
196
+ }
197
+ });
198
+ if (planned) return {
199
+ host: creatable(planned.host),
200
+ entries: planned.entries
201
+ };
202
+ }
203
+ const builtin = (theme == null ? void 0 : theme.kind) === 'builtin' ? theme : {
204
+ kind: 'builtin',
205
+ base: 'starter',
206
+ brand: {
207
+ light: {}
208
+ },
209
+ fonts: null
210
+ };
211
+ const preset = builtin.base === 'starter' ? null : (_presets_find = presets.find((entry)=>(entry == null ? void 0 : entry.id) === siteStarterPresetId(builtin.base))) != null ? _presets_find : null;
212
+ if (preset) {
213
+ const base = themeArtifactContent(preset.theme);
214
+ const planned = plan({
215
+ action: 'select',
216
+ target: {
217
+ kind: 'preset',
218
+ id: preset.id,
219
+ name: preset.name,
220
+ theme: base
221
+ }
222
+ });
223
+ if (planned) {
224
+ return {
225
+ host: creatable(_extends({}, planned.host, {
226
+ themeOverride: starterOverride(builtin, base)
227
+ })),
228
+ entries: planned.entries
229
+ };
230
+ }
231
+ }
232
+ return {
233
+ host: creatable({
234
+ theme: DEFAULT_SITE_THEME,
235
+ themeOverride: starterOverride(builtin, DEFAULT_SITE_THEME)
236
+ }),
237
+ entries: []
238
+ };
239
+ }
240
+
241
+ //# sourceMappingURL=site-starter-theme.js.map