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

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 (40) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +2 -2
  3. package/src/lib/app-utils/docs-help.generated.js +2 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +16 -9
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/enabled-plugins-context.d.ts +8 -4
  8. package/src/lib/app-utils/enabled-plugins-context.js +9 -4
  9. package/src/lib/app-utils/enabled-plugins-context.js.map +1 -1
  10. package/src/lib/app-utils/host-naming.d.ts +2 -29
  11. package/src/lib/app-utils/host-naming.js +5 -28
  12. package/src/lib/app-utils/host-naming.js.map +1 -1
  13. package/src/lib/app-utils/media-ref.js +1 -1
  14. package/src/lib/app-utils/media-ref.js.map +1 -1
  15. package/src/lib/app-utils/notifications.d.ts +7 -0
  16. package/src/lib/app-utils/notifications.js.map +1 -1
  17. package/src/lib/app-utils/plan-entitlements.js +14 -8
  18. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  19. package/src/lib/app-utils/platform-brand.d.ts +4 -0
  20. package/src/lib/app-utils/platform-brand.js +2 -0
  21. package/src/lib/app-utils/platform-brand.js.map +1 -1
  22. package/src/lib/app-utils/plugin-bundle-checks.js +193 -15
  23. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  24. package/src/lib/app-utils/realm-elements-context.d.ts +17 -0
  25. package/src/lib/app-utils/realm-elements-context.js +35 -0
  26. package/src/lib/app-utils/realm-elements-context.js.map +1 -0
  27. package/src/lib/app-utils/tenant-apex.d.ts +45 -0
  28. package/src/lib/app-utils/tenant-apex.js +48 -0
  29. package/src/lib/app-utils/tenant-apex.js.map +1 -0
  30. package/src/lib/app-utils/wire-nodes.d.ts +64 -0
  31. package/src/lib/app-utils/wire-nodes.js +112 -0
  32. package/src/lib/app-utils/wire-nodes.js.map +1 -0
  33. package/src/lib/foundation/definitions/org-billing.types.d.ts +12 -0
  34. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  35. package/src/lib/plugin-manager/plugin-contributions.d.ts +8 -3
  36. package/src/lib/plugin-manager/plugin-contributions.js +5 -3
  37. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  38. package/src/lib/plugin-manager/realm-plugins.d.ts +70 -0
  39. package/src/lib/plugin-manager/realm-plugins.js +143 -29
  40. package/src/lib/plugin-manager/realm-plugins.js.map +1 -1
@@ -0,0 +1,35 @@
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 { createContext } from "react";
17
+ /**
18
+ * How a namespaced element waits for the realm bundle that registers it
19
+ * (AGL-3390).
20
+ *
21
+ * A signed marketplace plugin's elements render on the server, and the
22
+ * browser has to register the same component before it can hydrate that
23
+ * HTML. The host app answers, for a component id, the load that registers
24
+ * it, or `undefined` when no load on this page will. The renderer suspends a
25
+ * namespaced element on that load inside the element's own Suspense
26
+ * boundary, so React keeps the server's HTML in place until the bundle has
27
+ * run, and the rest of the page hydrates without waiting.
28
+ *
29
+ * `undefined` as a whole means the surface loads no realm bundles in the
30
+ * render path (the Besigner loads them before its canvas mounts), and no
31
+ * element waits.
32
+ */ export const RealmElementsContext = createContext(undefined);
33
+ RealmElementsContext.displayName = 'RealmElementsContext';
34
+
35
+ //# sourceMappingURL=realm-elements-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/realm-elements-context.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 */\nimport { createContext } from 'react'\n\n/**\n * How a namespaced element waits for the realm bundle that registers it\n * (AGL-3390).\n *\n * A signed marketplace plugin's elements render on the server, and the\n * browser has to register the same component before it can hydrate that\n * HTML. The host app answers, for a component id, the load that registers\n * it, or `undefined` when no load on this page will. The renderer suspends a\n * namespaced element on that load inside the element's own Suspense\n * boundary, so React keeps the server's HTML in place until the bundle has\n * run, and the rest of the page hydrates without waiting.\n *\n * `undefined` as a whole means the surface loads no realm bundles in the\n * render path (the Besigner loads them before its canvas mounts), and no\n * element waits.\n */\nexport const RealmElementsContext = createContext<\n ((componentId: string) => Promise<void> | undefined) | undefined\n>(undefined)\nRealmElementsContext.displayName = 'RealmElementsContext'\n"],"names":["createContext","RealmElementsContext","undefined","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,aAAa,QAAQ,QAAO;AAErC;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,uBAAuBD,cAElCE,WAAU;AACZD,qBAAqBE,WAAW,GAAG"}
@@ -0,0 +1,45 @@
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
+ /**
18
+ * The apex tenant sites are served from. Console lives on `app.aglyn.com`.
19
+ *
20
+ * CONFIGURABLE, not a literal (AGL-2121). AGL-2022 moved the console's
21
+ * *display* links onto `NEXT_PUBLIC_TENANT_DOMAIN` and shipped a green spec
22
+ * for it; this constant — which every OTHER consumer reads — stayed a bare
23
+ * `'aglyn.app'`. So a self-hoster who set the variable got correct "View live"
24
+ * links in their own console while the software went on emitting our apex
25
+ * everywhere that actually reaches the public: `<link rel="canonical">`,
26
+ * `/api/sitemap`, `/api/robots`, `/api/collections-rss` item links,
27
+ * `/api/manifest`, `og:image`, and the origin inbox-bound `<img src>` resolves
28
+ * against. Their customers' sites told Google, every feed reader and every
29
+ * inbox that they lived at a name the operator does not control and we do not
30
+ * serve for them.
31
+ *
32
+ * Same variable and same default as `tenant-links.ts` — one value, one name
33
+ * (AGL-733). Dot notation is load-bearing: this module reaches client bundles,
34
+ * and Next inlines `NEXT_PUBLIC_*` by substituting `process.env.NAME`
35
+ * textually; the bracket form is never substituted and would read `undefined`
36
+ * in the browser. Trimmed so a half-finished `.env` line reads as absent
37
+ * rather than as a whitespace apex.
38
+ *
39
+ * The asymmetry with `operator-identity.ts` — which refuses to default at all
40
+ * — is deliberate. A wrong apex is visible to the operator on the first click
41
+ * in their own console, so a default they can see and correct is a default. A
42
+ * wrong contact address fails silently and off-site, which is why that one
43
+ * refuses to guess.
44
+ */
45
+ export declare const TENANT_APEX: string;
@@ -0,0 +1,48 @@
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
+ */ // A leaf of its own (AGL-3401): `media-ref.ts` is on every published page and
17
+ // needs this one constant, and reaching it through `host-naming.ts` put the
18
+ // subdomain policy and the phishing screen's brand list on every page too.
19
+ /**
20
+ * The apex tenant sites are served from. Console lives on `app.aglyn.com`.
21
+ *
22
+ * CONFIGURABLE, not a literal (AGL-2121). AGL-2022 moved the console's
23
+ * *display* links onto `NEXT_PUBLIC_TENANT_DOMAIN` and shipped a green spec
24
+ * for it; this constant — which every OTHER consumer reads — stayed a bare
25
+ * `'aglyn.app'`. So a self-hoster who set the variable got correct "View live"
26
+ * links in their own console while the software went on emitting our apex
27
+ * everywhere that actually reaches the public: `<link rel="canonical">`,
28
+ * `/api/sitemap`, `/api/robots`, `/api/collections-rss` item links,
29
+ * `/api/manifest`, `og:image`, and the origin inbox-bound `<img src>` resolves
30
+ * against. Their customers' sites told Google, every feed reader and every
31
+ * inbox that they lived at a name the operator does not control and we do not
32
+ * serve for them.
33
+ *
34
+ * Same variable and same default as `tenant-links.ts` — one value, one name
35
+ * (AGL-733). Dot notation is load-bearing: this module reaches client bundles,
36
+ * and Next inlines `NEXT_PUBLIC_*` by substituting `process.env.NAME`
37
+ * textually; the bracket form is never substituted and would read `undefined`
38
+ * in the browser. Trimmed so a half-finished `.env` line reads as absent
39
+ * rather than as a whitespace apex.
40
+ *
41
+ * The asymmetry with `operator-identity.ts` — which refuses to default at all
42
+ * — is deliberate. A wrong apex is visible to the operator on the first click
43
+ * in their own console, so a default they can see and correct is a default. A
44
+ * wrong contact address fails silently and off-site, which is why that one
45
+ * refuses to guess.
46
+ */ export const TENANT_APEX = (process.env.NEXT_PUBLIC_TENANT_DOMAIN || '').trim() || 'aglyn.app';
47
+
48
+ //# sourceMappingURL=tenant-apex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/tenant-apex.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// A leaf of its own (AGL-3401): `media-ref.ts` is on every published page and\n// needs this one constant, and reaching it through `host-naming.ts` put the\n// subdomain policy and the phishing screen's brand list on every page too.\n\n/**\n * The apex tenant sites are served from. Console lives on `app.aglyn.com`.\n *\n * CONFIGURABLE, not a literal (AGL-2121). AGL-2022 moved the console's\n * *display* links onto `NEXT_PUBLIC_TENANT_DOMAIN` and shipped a green spec\n * for it; this constant — which every OTHER consumer reads — stayed a bare\n * `'aglyn.app'`. So a self-hoster who set the variable got correct \"View live\"\n * links in their own console while the software went on emitting our apex\n * everywhere that actually reaches the public: `<link rel=\"canonical\">`,\n * `/api/sitemap`, `/api/robots`, `/api/collections-rss` item links,\n * `/api/manifest`, `og:image`, and the origin inbox-bound `<img src>` resolves\n * against. Their customers' sites told Google, every feed reader and every\n * inbox that they lived at a name the operator does not control and we do not\n * serve for them.\n *\n * Same variable and same default as `tenant-links.ts` — one value, one name\n * (AGL-733). Dot notation is load-bearing: this module reaches client bundles,\n * and Next inlines `NEXT_PUBLIC_*` by substituting `process.env.NAME`\n * textually; the bracket form is never substituted and would read `undefined`\n * in the browser. Trimmed so a half-finished `.env` line reads as absent\n * rather than as a whitespace apex.\n *\n * The asymmetry with `operator-identity.ts` — which refuses to default at all\n * — is deliberate. A wrong apex is visible to the operator on the first click\n * in their own console, so a default they can see and correct is a default. A\n * wrong contact address fails silently and off-site, which is why that one\n * refuses to guess.\n */\nexport const TENANT_APEX =\n (process.env.NEXT_PUBLIC_TENANT_DOMAIN || '').trim() || 'aglyn.app'\n"],"names":["TENANT_APEX","process","env","NEXT_PUBLIC_TENANT_DOMAIN","trim"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GACD,OAAO,MAAMA,cACX,AAACC,CAAAA,QAAQC,GAAG,CAACC,yBAAyB,IAAI,EAAC,EAAGC,IAAI,MAAM,YAAW"}
@@ -0,0 +1,64 @@
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
+ /**
18
+ * A published page's node map as it crosses from the server into the
19
+ * browser, without the two fields the map already states (AGL-3401).
20
+ *
21
+ * The map rides in the page's flight payload, inside its HTML, and on
22
+ * aglyn.com it was 242 KB of it. Every node repeats its own key as `$id` and
23
+ * its parent's key as `parentId`, though the key IS the id and the parent's
24
+ * `nodes` list already says whose child it is. Both are random ids, which is
25
+ * the part of the map compression does least with: dropping them took the
26
+ * map from 34.5 KB to about 28.5 KB gzipped on that page.
27
+ *
28
+ * `packNodesForWire` drops a field only when `unpackWireNodes` will put back
29
+ * the same value — an `$id` that differs from its key, or a `parentId` no
30
+ * single parent's `nodes` list agrees with, is left in place — so a node that
31
+ * carries both comes back exactly as it left, well-formed map or not. (One
32
+ * that carried neither comes back with them.) Unpacking is idempotent, which is
33
+ * what lets a full map (a withheld panel's patch from `/api/screen/nodes`, a
34
+ * members-only tree) be merged over a packed one and unpacked together.
35
+ *
36
+ * Banner-free and dependency-free: the tenant's server page packs and its
37
+ * client root unpacks, so it must be importable from both.
38
+ */
39
+ /** A node as the map carries it: an object, keyed by its id. */
40
+ type WireNode = Record<string, unknown> & {
41
+ $id?: unknown;
42
+ parentId?: unknown;
43
+ nodes?: unknown;
44
+ };
45
+ /** A node map, packed or not. */
46
+ export type WireNodeMap<N = WireNode> = Record<string, N | null | undefined>;
47
+ /**
48
+ * The map without each `$id` that equals its key and each `parentId` its
49
+ * parent's `nodes` list states. A new map of new node objects; the argument
50
+ * is never mutated, because the server's is the cached loader's.
51
+ *
52
+ * Typed as the map it is handed rather than as a looser one: the packed form
53
+ * has one reader, `unpackWireNodes`, and the prop it travels in is read
54
+ * nowhere else, so a second type would buy a cast at both ends.
55
+ */
56
+ export declare function packNodesForWire<M extends WireNodeMap | null | undefined>(nodes: M): M;
57
+ /**
58
+ * The map with every node's `$id` and `parentId` back, from its key and its
59
+ * parent's `nodes` list. A node that already carries either keeps its own,
60
+ * so an unpacked map comes back equal and a packed one merged with an
61
+ * unpacked patch comes back whole.
62
+ */
63
+ export declare function unpackWireNodes<M extends WireNodeMap | null | undefined>(nodes: M): M;
64
+ export {};
@@ -0,0 +1,112 @@
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
+ * A published page's node map as it crosses from the server into the
18
+ * browser, without the two fields the map already states (AGL-3401).
19
+ *
20
+ * The map rides in the page's flight payload, inside its HTML, and on
21
+ * aglyn.com it was 242 KB of it. Every node repeats its own key as `$id` and
22
+ * its parent's key as `parentId`, though the key IS the id and the parent's
23
+ * `nodes` list already says whose child it is. Both are random ids, which is
24
+ * the part of the map compression does least with: dropping them took the
25
+ * map from 34.5 KB to about 28.5 KB gzipped on that page.
26
+ *
27
+ * `packNodesForWire` drops a field only when `unpackWireNodes` will put back
28
+ * the same value — an `$id` that differs from its key, or a `parentId` no
29
+ * single parent's `nodes` list agrees with, is left in place — so a node that
30
+ * carries both comes back exactly as it left, well-formed map or not. (One
31
+ * that carried neither comes back with them.) Unpacking is idempotent, which is
32
+ * what lets a full map (a withheld panel's patch from `/api/screen/nodes`, a
33
+ * members-only tree) be merged over a packed one and unpacked together.
34
+ *
35
+ * Banner-free and dependency-free: the tenant's server page packs and its
36
+ * client root unpacks, so it must be importable from both.
37
+ */ /** A node as the map carries it: an object, keyed by its id. */ import { _ as _extends } from "@swc/helpers/_/_extends";
38
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
39
+ /** Each child id to the key of the node whose `nodes` list names it. */ function parentsOf(nodes) {
40
+ const parents = new Map();
41
+ const claimed = new Set();
42
+ for (const [key, node] of Object.entries(nodes)){
43
+ const children = node == null ? void 0 : node.nodes;
44
+ if (!Array.isArray(children)) continue;
45
+ for (const child of children){
46
+ if (typeof child !== 'string') continue;
47
+ // A child two parents list has no single answer, so neither may stand
48
+ // in for the `parentId` it stores.
49
+ if (claimed.has(child)) parents.delete(child);
50
+ else parents.set(child, key);
51
+ claimed.add(child);
52
+ }
53
+ }
54
+ return parents;
55
+ }
56
+ /**
57
+ * The map without each `$id` that equals its key and each `parentId` its
58
+ * parent's `nodes` list states. A new map of new node objects; the argument
59
+ * is never mutated, because the server's is the cached loader's.
60
+ *
61
+ * Typed as the map it is handed rather than as a looser one: the packed form
62
+ * has one reader, `unpackWireNodes`, and the prop it travels in is read
63
+ * nowhere else, so a second type would buy a cast at both ends.
64
+ */ export function packNodesForWire(nodes) {
65
+ if (!nodes) return nodes;
66
+ const parents = parentsOf(nodes);
67
+ const packed = {};
68
+ for (const [key, node] of Object.entries(nodes)){
69
+ if (!node || typeof node !== 'object') {
70
+ packed[key] = node;
71
+ continue;
72
+ }
73
+ const { $id, parentId } = node, rest = _object_without_properties_loose(node, [
74
+ "$id",
75
+ "parentId"
76
+ ]);
77
+ const out = rest;
78
+ if ($id !== key) out.$id = $id;
79
+ if (parentId !== undefined && parentId !== parents.get(key)) {
80
+ out.parentId = parentId;
81
+ }
82
+ packed[key] = out;
83
+ }
84
+ return packed;
85
+ }
86
+ /**
87
+ * The map with every node's `$id` and `parentId` back, from its key and its
88
+ * parent's `nodes` list. A node that already carries either keeps its own,
89
+ * so an unpacked map comes back equal and a packed one merged with an
90
+ * unpacked patch comes back whole.
91
+ */ export function unpackWireNodes(nodes) {
92
+ if (!nodes) return nodes;
93
+ const parents = parentsOf(nodes);
94
+ const unpacked = {};
95
+ for (const [key, node] of Object.entries(nodes)){
96
+ if (!node || typeof node !== 'object') {
97
+ unpacked[key] = node;
98
+ continue;
99
+ }
100
+ const parent = parents.get(key);
101
+ const needsId = !('$id' in node);
102
+ const needsParent = !('parentId' in node) && parent !== undefined;
103
+ unpacked[key] = needsId || needsParent ? _extends({}, needsId ? {
104
+ $id: key
105
+ } : {}, node, needsParent ? {
106
+ parentId: parent
107
+ } : {}) : node;
108
+ }
109
+ return unpacked;
110
+ }
111
+
112
+ //# sourceMappingURL=wire-nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/wire-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\n/**\n * A published page's node map as it crosses from the server into the\n * browser, without the two fields the map already states (AGL-3401).\n *\n * The map rides in the page's flight payload, inside its HTML, and on\n * aglyn.com it was 242 KB of it. Every node repeats its own key as `$id` and\n * its parent's key as `parentId`, though the key IS the id and the parent's\n * `nodes` list already says whose child it is. Both are random ids, which is\n * the part of the map compression does least with: dropping them took the\n * map from 34.5 KB to about 28.5 KB gzipped on that page.\n *\n * `packNodesForWire` drops a field only when `unpackWireNodes` will put back\n * the same value — an `$id` that differs from its key, or a `parentId` no\n * single parent's `nodes` list agrees with, is left in place — so a node that\n * carries both comes back exactly as it left, well-formed map or not. (One\n * that carried neither comes back with them.) Unpacking is idempotent, which is\n * what lets a full map (a withheld panel's patch from `/api/screen/nodes`, a\n * members-only tree) be merged over a packed one and unpacked together.\n *\n * Banner-free and dependency-free: the tenant's server page packs and its\n * client root unpacks, so it must be importable from both.\n */\n\n/** A node as the map carries it: an object, keyed by its id. */\ntype WireNode = Record<string, unknown> & {\n $id?: unknown\n parentId?: unknown\n nodes?: unknown\n}\n\n/** A node map, packed or not. */\nexport type WireNodeMap<N = WireNode> = Record<string, N | null | undefined>\n\n/** Each child id to the key of the node whose `nodes` list names it. */\nfunction parentsOf(nodes: WireNodeMap): Map<string, string> {\n const parents = new Map<string, string>()\n const claimed = new Set<string>()\n for (const [key, node] of Object.entries(nodes)) {\n const children = node?.nodes\n if (!Array.isArray(children)) continue\n for (const child of children) {\n if (typeof child !== 'string') continue\n // A child two parents list has no single answer, so neither may stand\n // in for the `parentId` it stores.\n if (claimed.has(child)) parents.delete(child)\n else parents.set(child, key)\n claimed.add(child)\n }\n }\n return parents\n}\n\n/**\n * The map without each `$id` that equals its key and each `parentId` its\n * parent's `nodes` list states. A new map of new node objects; the argument\n * is never mutated, because the server's is the cached loader's.\n *\n * Typed as the map it is handed rather than as a looser one: the packed form\n * has one reader, `unpackWireNodes`, and the prop it travels in is read\n * nowhere else, so a second type would buy a cast at both ends.\n */\nexport function packNodesForWire<M extends WireNodeMap | null | undefined>(\n nodes: M,\n): M {\n if (!nodes) return nodes\n const parents = parentsOf(nodes)\n const packed: WireNodeMap = {}\n for (const [key, node] of Object.entries(nodes)) {\n if (!node || typeof node !== 'object') {\n packed[key] = node\n continue\n }\n const { $id, parentId, ...rest } = node\n const out: WireNode = rest\n if ($id !== key) out.$id = $id\n if (parentId !== undefined && parentId !== parents.get(key)) {\n out.parentId = parentId\n }\n packed[key] = out\n }\n return packed as M\n}\n\n/**\n * The map with every node's `$id` and `parentId` back, from its key and its\n * parent's `nodes` list. A node that already carries either keeps its own,\n * so an unpacked map comes back equal and a packed one merged with an\n * unpacked patch comes back whole.\n */\nexport function unpackWireNodes<M extends WireNodeMap | null | undefined>(\n nodes: M,\n): M {\n if (!nodes) return nodes\n const parents = parentsOf(nodes)\n const unpacked: WireNodeMap = {}\n for (const [key, node] of Object.entries(nodes)) {\n if (!node || typeof node !== 'object') {\n unpacked[key] = node\n continue\n }\n const parent = parents.get(key)\n const needsId = !('$id' in node)\n const needsParent = !('parentId' in node) && parent !== undefined\n unpacked[key] =\n needsId || needsParent\n ? {\n ...(needsId ? { $id: key } : {}),\n ...node,\n ...(needsParent ? { parentId: parent } : {}),\n }\n : node\n }\n return unpacked as M\n}\n"],"names":["parentsOf","nodes","parents","Map","claimed","Set","key","node","Object","entries","children","Array","isArray","child","has","delete","set","add","packNodesForWire","packed","$id","parentId","rest","out","undefined","get","unpackWireNodes","unpacked","parent","needsId","needsParent"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,8DAA8D;;AAU9D,sEAAsE,GACtE,SAASA,UAAUC,KAAkB;IACnC,MAAMC,UAAU,IAAIC;IACpB,MAAMC,UAAU,IAAIC;IACpB,KAAK,MAAM,CAACC,KAAKC,KAAK,IAAIC,OAAOC,OAAO,CAACR,OAAQ;QAC/C,MAAMS,WAAWH,wBAAAA,KAAMN,KAAK;QAC5B,IAAI,CAACU,MAAMC,OAAO,CAACF,WAAW;QAC9B,KAAK,MAAMG,SAASH,SAAU;YAC5B,IAAI,OAAOG,UAAU,UAAU;YAC/B,sEAAsE;YACtE,mCAAmC;YACnC,IAAIT,QAAQU,GAAG,CAACD,QAAQX,QAAQa,MAAM,CAACF;iBAClCX,QAAQc,GAAG,CAACH,OAAOP;YACxBF,QAAQa,GAAG,CAACJ;QACd;IACF;IACA,OAAOX;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASgB,iBACdjB,KAAQ;IAER,IAAI,CAACA,OAAO,OAAOA;IACnB,MAAMC,UAAUF,UAAUC;IAC1B,MAAMkB,SAAsB,CAAC;IAC7B,KAAK,MAAM,CAACb,KAAKC,KAAK,IAAIC,OAAOC,OAAO,CAACR,OAAQ;QAC/C,IAAI,CAACM,QAAQ,OAAOA,SAAS,UAAU;YACrCY,MAAM,CAACb,IAAI,GAAGC;YACd;QACF;QACA,MAAM,EAAEa,GAAG,EAAEC,QAAQ,EAAW,GAAGd,MAATe,wCAASf;;;;QACnC,MAAMgB,MAAgBD;QACtB,IAAIF,QAAQd,KAAKiB,IAAIH,GAAG,GAAGA;QAC3B,IAAIC,aAAaG,aAAaH,aAAanB,QAAQuB,GAAG,CAACnB,MAAM;YAC3DiB,IAAIF,QAAQ,GAAGA;QACjB;QACAF,MAAM,CAACb,IAAI,GAAGiB;IAChB;IACA,OAAOJ;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASO,gBACdzB,KAAQ;IAER,IAAI,CAACA,OAAO,OAAOA;IACnB,MAAMC,UAAUF,UAAUC;IAC1B,MAAM0B,WAAwB,CAAC;IAC/B,KAAK,MAAM,CAACrB,KAAKC,KAAK,IAAIC,OAAOC,OAAO,CAACR,OAAQ;QAC/C,IAAI,CAACM,QAAQ,OAAOA,SAAS,UAAU;YACrCoB,QAAQ,CAACrB,IAAI,GAAGC;YAChB;QACF;QACA,MAAMqB,SAAS1B,QAAQuB,GAAG,CAACnB;QAC3B,MAAMuB,UAAU,CAAE,CAAA,SAAStB,IAAG;QAC9B,MAAMuB,cAAc,CAAE,CAAA,cAAcvB,IAAG,KAAMqB,WAAWJ;QACxDG,QAAQ,CAACrB,IAAI,GACXuB,WAAWC,cACP,aACMD,UAAU;YAAET,KAAKd;QAAI,IAAI,CAAC,GAC3BC,MACCuB,cAAc;YAAET,UAAUO;QAAO,IAAI,CAAC,KAE5CrB;IACR;IACA,OAAOoB;AACT"}
@@ -296,8 +296,20 @@ export interface OrgBrandingProfile {
296
296
  productName?: string;
297
297
  /** Primary/full-color logo URL (light backgrounds, console chrome). */
298
298
  logoUrl?: string;
299
+ /**
300
+ * The logo for DARK grounds (AGL-3406): the console chrome in dark mode and the
301
+ * published site's attribution badge, which always sits on a dark pill.
302
+ * Unset falls back to {@link logoUrl}.
303
+ */
304
+ logoDarkUrl?: string;
299
305
  /** Favicon URL for branded surfaces. */
300
306
  faviconUrl?: string;
307
+ /**
308
+ * The favicon for a browser whose tab strip is dark, emitted as a second
309
+ * `<link rel="icon">` under `(prefers-color-scheme: dark)`. Unset falls back
310
+ * to {@link faviconUrl}.
311
+ */
312
+ faviconDarkUrl?: string;
301
313
  /** Brand primary color as a CSS color (hex), e.g. `#1a73e8`. */
302
314
  primaryColor?: string;
303
315
  /** Support/help destination linked from branded surfaces and emails. */