@aglyn/aglyn 1.0.0-beta.271 → 1.0.0-beta.272
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.
- package/package.json +11 -11
- package/src/lib/app-utils/account-preferences.d.ts +50 -0
- package/src/lib/app-utils/account-preferences.js +96 -0
- package/src/lib/app-utils/account-preferences.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +3 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/client-build.d.ts +4 -1
- package/src/lib/app-utils/client-build.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +6 -1
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +7 -1
- package/src/lib/app-utils/docs-help.generated.js +22 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +6 -6
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/plan-card-meters.d.ts +96 -0
- package/src/lib/app-utils/plan-card-meters.js +197 -0
- package/src/lib/app-utils/plan-card-meters.js.map +1 -0
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +12 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/site-care.d.ts +25 -2
- package/src/lib/app-utils/site-care.js +28 -1
- package/src/lib/app-utils/site-care.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +20 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +16 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-media-ingest.d.ts +18 -1
- package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-resources.d.ts +74 -0
- package/src/lib/plugin-manager/plugin-site-resources.js +135 -0
- package/src/lib/plugin-manager/plugin-site-resources.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/aglyn",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.272",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
42
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
43
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
44
|
-
"@aglyn/shared-util-first-touch": "1.0.0-beta.
|
|
45
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
46
|
-
"@aglyn/shared-util-logger": "1.0.0-beta.
|
|
47
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
48
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
49
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
40
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.272",
|
|
41
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.272",
|
|
42
|
+
"@aglyn/shared-data-types": "1.0.0-beta.272",
|
|
43
|
+
"@aglyn/shared-util-email": "1.0.0-beta.272",
|
|
44
|
+
"@aglyn/shared-util-first-touch": "1.0.0-beta.272",
|
|
45
|
+
"@aglyn/shared-util-http": "1.0.0-beta.272",
|
|
46
|
+
"@aglyn/shared-util-logger": "1.0.0-beta.272",
|
|
47
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.272",
|
|
48
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.272",
|
|
49
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.272",
|
|
50
50
|
"@data-driven-forms/react-form-renderer": "^4.2.0",
|
|
51
51
|
"@msgpack/msgpack": "^3.1.3",
|
|
52
52
|
"@types/unist": "^3.0.3",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* | field | shape | cap |
|
|
24
24
|
* | -- | -- | -- |
|
|
25
25
|
* | `pinnedOrgs` | workspace ids, in the order the person keeps them | 5 |
|
|
26
|
+
* | `pinnedHosts` | {@link PinnedHost}s, in the order they were pinned | 5 a workspace, 100 in all |
|
|
26
27
|
* | `recentOrgs` | workspace ids, most recently opened first | 8 |
|
|
27
28
|
* | `starred` | {@link StarredItem}s, in the order they were starred | 50 |
|
|
28
29
|
* | `recents` | {@link RecentItem}s, most recently opened first | 30 |
|
|
@@ -182,6 +183,55 @@ export declare function replacePinnedOrg(pinned: readonly string[], replacing: s
|
|
|
182
183
|
export declare function keepPinnedOrgs(stored: readonly string[], keep: readonly string[]): string[];
|
|
183
184
|
/** The pins with one moved up (`-1`) or down (`1`) a place; unchanged at an end. */
|
|
184
185
|
export declare function movePinnedOrg(pinned: readonly string[], orgId: string, offset: number): string[];
|
|
186
|
+
/** `users/{uid}` field holding the sites the person pinned, each with its workspace (AGL-4005). */
|
|
187
|
+
export declare const PINNED_HOSTS_FIELD = "pinnedHosts";
|
|
188
|
+
/** The most sites a person may pin in one workspace: five, as for workspaces. */
|
|
189
|
+
export declare const PINNED_HOSTS_PER_WORKSPACE = 5;
|
|
190
|
+
/**
|
|
191
|
+
* The most site pins the account keeps across every workspace, the bound the
|
|
192
|
+
* Firestore rules hold. Nobody meets it by pinning: it takes twenty
|
|
193
|
+
* workspaces with five each. Past it, the oldest pins of OTHER workspaces
|
|
194
|
+
* give way (see {@link pinHost}).
|
|
195
|
+
*/
|
|
196
|
+
export declare const PINNED_HOSTS_MAX = 100;
|
|
197
|
+
/**
|
|
198
|
+
* One pinned site and the workspace it belongs to. The workspace rides with
|
|
199
|
+
* the pin so a surface that holds only one workspace's sites (the site
|
|
200
|
+
* switcher's window) can tell its own pins apart without reading every site.
|
|
201
|
+
*/
|
|
202
|
+
export interface PinnedHost {
|
|
203
|
+
orgId: string;
|
|
204
|
+
hostId: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Every site pin the person stored, in the order pinned, without repeats.
|
|
208
|
+
* Anything that is not a `{ orgId, hostId }` reads as no pin, so a stray
|
|
209
|
+
* value can never take the sites list down.
|
|
210
|
+
*/
|
|
211
|
+
export declare function readPinnedHosts(profile: unknown): PinnedHost[];
|
|
212
|
+
/** One workspace's pinned site ids, in the order pinned, at most five. */
|
|
213
|
+
export declare function workspacePinnedHosts(stored: readonly PinnedHost[], orgId: string): string[];
|
|
214
|
+
export interface PinHostResult {
|
|
215
|
+
/** Every stored pin after the request: unchanged unless `outcome` is `pinned`. */
|
|
216
|
+
pinnedHosts: PinnedHost[];
|
|
217
|
+
outcome: PinOrgOutcome;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Pins a site at the end of its workspace's pins, or says that workspace
|
|
221
|
+
* already has five. As with workspaces, a full list is never made room in
|
|
222
|
+
* quietly: the caller asks "Pinned is full. Replace one?" and calls
|
|
223
|
+
* {@link replacePinnedHost} with the answer. Only the account-wide bound
|
|
224
|
+
* ({@link PINNED_HOSTS_MAX}) makes room, and only from other workspaces.
|
|
225
|
+
*/
|
|
226
|
+
export declare function pinHost(stored: readonly PinnedHost[], pin: PinnedHost): PinHostResult;
|
|
227
|
+
/** The stored pins without one site. */
|
|
228
|
+
export declare function unpinHost(stored: readonly PinnedHost[], hostId: string): PinnedHost[];
|
|
229
|
+
/**
|
|
230
|
+
* The stored pins with `pin` in the place `replacing` held in the same
|
|
231
|
+
* workspace: the answer to "Pinned is full. Replace one?". Unchanged when
|
|
232
|
+
* `replacing` is not one of that workspace's pins or `pin` already is.
|
|
233
|
+
*/
|
|
234
|
+
export declare function replacePinnedHost(stored: readonly PinnedHost[], replacing: string, pin: PinnedHost): PinnedHost[];
|
|
185
235
|
/** The recently opened workspaces as stored, newest first. */
|
|
186
236
|
export declare function readRecentOrgs(profile: unknown): string[];
|
|
187
237
|
/** The recent workspaces after opening one: it moves to the front, the oldest falls off. */
|
|
@@ -23,6 +23,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
23
23
|
* | field | shape | cap |
|
|
24
24
|
* | -- | -- | -- |
|
|
25
25
|
* | `pinnedOrgs` | workspace ids, in the order the person keeps them | 5 |
|
|
26
|
+
* | `pinnedHosts` | {@link PinnedHost}s, in the order they were pinned | 5 a workspace, 100 in all |
|
|
26
27
|
* | `recentOrgs` | workspace ids, most recently opened first | 8 |
|
|
27
28
|
* | `starred` | {@link StarredItem}s, in the order they were starred | 50 |
|
|
28
29
|
* | `recents` | {@link RecentItem}s, most recently opened first | 30 |
|
|
@@ -191,6 +192,101 @@ function idList(value) {
|
|
|
191
192
|
next[to] = orgId;
|
|
192
193
|
return next;
|
|
193
194
|
}
|
|
195
|
+
// ── Site pins ───────────────────────────────────────────────────────────────
|
|
196
|
+
/** `users/{uid}` field holding the sites the person pinned, each with its workspace (AGL-4005). */ export const PINNED_HOSTS_FIELD = 'pinnedHosts';
|
|
197
|
+
/** The most sites a person may pin in one workspace: five, as for workspaces. */ export const PINNED_HOSTS_PER_WORKSPACE = 5;
|
|
198
|
+
/**
|
|
199
|
+
* The most site pins the account keeps across every workspace, the bound the
|
|
200
|
+
* Firestore rules hold. Nobody meets it by pinning: it takes twenty
|
|
201
|
+
* workspaces with five each. Past it, the oldest pins of OTHER workspaces
|
|
202
|
+
* give way (see {@link pinHost}).
|
|
203
|
+
*/ export const PINNED_HOSTS_MAX = 100;
|
|
204
|
+
function pinnedHostList(value) {
|
|
205
|
+
if (!Array.isArray(value)) return [];
|
|
206
|
+
const seen = new Set();
|
|
207
|
+
const pins = [];
|
|
208
|
+
for (const entry of value){
|
|
209
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
210
|
+
const { orgId, hostId } = entry;
|
|
211
|
+
if (typeof orgId !== 'string' || !orgId) continue;
|
|
212
|
+
if (typeof hostId !== 'string' || !hostId || seen.has(hostId)) continue;
|
|
213
|
+
seen.add(hostId);
|
|
214
|
+
pins.push({
|
|
215
|
+
orgId,
|
|
216
|
+
hostId
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return pins;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Every site pin the person stored, in the order pinned, without repeats.
|
|
223
|
+
* Anything that is not a `{ orgId, hostId }` reads as no pin, so a stray
|
|
224
|
+
* value can never take the sites list down.
|
|
225
|
+
*/ export function readPinnedHosts(profile) {
|
|
226
|
+
return pinnedHostList(field(profile, PINNED_HOSTS_FIELD));
|
|
227
|
+
}
|
|
228
|
+
/** One workspace's pinned site ids, in the order pinned, at most five. */ export function workspacePinnedHosts(stored, orgId) {
|
|
229
|
+
return pinnedHostList(stored).filter((pin)=>pin.orgId === orgId).map((pin)=>pin.hostId).slice(0, PINNED_HOSTS_PER_WORKSPACE);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Pins a site at the end of its workspace's pins, or says that workspace
|
|
233
|
+
* already has five. As with workspaces, a full list is never made room in
|
|
234
|
+
* quietly: the caller asks "Pinned is full. Replace one?" and calls
|
|
235
|
+
* {@link replacePinnedHost} with the answer. Only the account-wide bound
|
|
236
|
+
* ({@link PINNED_HOSTS_MAX}) makes room, and only from other workspaces.
|
|
237
|
+
*/ export function pinHost(stored, pin) {
|
|
238
|
+
const current = pinnedHostList(stored);
|
|
239
|
+
if (!pin.orgId || !pin.hostId) return {
|
|
240
|
+
pinnedHosts: current,
|
|
241
|
+
outcome: 'alreadyPinned'
|
|
242
|
+
};
|
|
243
|
+
const inWorkspace = workspacePinnedHosts(current, pin.orgId);
|
|
244
|
+
if (inWorkspace.includes(pin.hostId)) return {
|
|
245
|
+
pinnedHosts: current,
|
|
246
|
+
outcome: 'alreadyPinned'
|
|
247
|
+
};
|
|
248
|
+
if (inWorkspace.length >= PINNED_HOSTS_PER_WORKSPACE) {
|
|
249
|
+
return {
|
|
250
|
+
pinnedHosts: current,
|
|
251
|
+
outcome: 'full'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// A site pinned under the workspace it has since moved out of moves with it.
|
|
255
|
+
const next = [
|
|
256
|
+
...current.filter((entry)=>entry.hostId !== pin.hostId),
|
|
257
|
+
{
|
|
258
|
+
orgId: pin.orgId,
|
|
259
|
+
hostId: pin.hostId
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
while(next.length > PINNED_HOSTS_MAX){
|
|
263
|
+
const oldest = next.findIndex((entry)=>entry.orgId !== pin.orgId);
|
|
264
|
+
if (oldest === -1) break;
|
|
265
|
+
next.splice(oldest, 1);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
pinnedHosts: next,
|
|
269
|
+
outcome: 'pinned'
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/** The stored pins without one site. */ export function unpinHost(stored, hostId) {
|
|
273
|
+
return pinnedHostList(stored).filter((entry)=>entry.hostId !== hostId);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The stored pins with `pin` in the place `replacing` held in the same
|
|
277
|
+
* workspace: the answer to "Pinned is full. Replace one?". Unchanged when
|
|
278
|
+
* `replacing` is not one of that workspace's pins or `pin` already is.
|
|
279
|
+
*/ export function replacePinnedHost(stored, replacing, pin) {
|
|
280
|
+
const current = pinnedHostList(stored);
|
|
281
|
+
const inWorkspace = workspacePinnedHosts(current, pin.orgId);
|
|
282
|
+
if (!pin.hostId || inWorkspace.includes(pin.hostId) || !inWorkspace.includes(replacing)) {
|
|
283
|
+
return current;
|
|
284
|
+
}
|
|
285
|
+
return current.filter((entry)=>entry.hostId !== pin.hostId).map((entry)=>entry.hostId === replacing ? {
|
|
286
|
+
orgId: pin.orgId,
|
|
287
|
+
hostId: pin.hostId
|
|
288
|
+
} : entry);
|
|
289
|
+
}
|
|
194
290
|
// ── Recent workspaces and the rail ──────────────────────────────────────────
|
|
195
291
|
/** The recently opened workspaces as stored, newest first. */ export function readRecentOrgs(profile) {
|
|
196
292
|
return idList(field(profile, RECENT_ORGS_FIELD)).slice(0, RECENT_ORGS_MAX);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/account-preferences.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 PREFERENCES A PERSON KEEPS ON THEIR ACCOUNT (AGL-3943).\n *\n * One documented shape on `users/{uid}`, read and written the same way by\n * the console and both native apps (docs/mobile/native-navigation.md):\n *\n * | field | shape | cap |\n * | -- | -- | -- |\n * | `pinnedOrgs` | workspace ids, in the order the person keeps them | 5 |\n * | `recentOrgs` | workspace ids, most recently opened first | 8 |\n * | `starred` | {@link StarredItem}s, in the order they were starred | 50 |\n * | `recents` | {@link RecentItem}s, most recently opened first | 30 |\n * | `workspaceColors` | workspace id → `#RRGGBB` the person picked | 100 |\n * | `appTheme` | {@link AppThemePrefs}, the apps' look, synced | — |\n *\n * The caps are held twice: here, by every writer, and by the Firestore rules,\n * which refuse a write that grows a list past its cap. A list that is ALREADY\n * past it (pins made before the cap, Zach 2026-10-18) may only shrink, so a\n * person with seven pins can still unpin one; the console asks them once\n * which five to keep, and until they answer the first five are the pins.\n *\n * A per-device override of the theme (\"this device only\") is the device's\n * own, kept in local storage on each platform, and never written here.\n *\n * NO IMPORTS, on purpose: the native apps read these constants, types and\n * the answers of these functions through the generated contracts, so it must\n * stay a plain module.\n */\n\n/** `users/{uid}` field holding the ids of the workspaces the person pinned. */\nexport const PINNED_ORGS_FIELD = 'pinnedOrgs'\n\n/** The most workspaces a person may pin, everywhere, the console included. */\nexport const PINNED_ORGS_MAX = 5\n\n/** `users/{uid}` field holding the workspaces opened most recently, newest first. */\nexport const RECENT_ORGS_FIELD = 'recentOrgs'\n\n/** How many recently opened workspaces are kept. */\nexport const RECENT_ORGS_MAX = 8\n\n/** How many recent workspaces the rail shows below the pins. */\nexport const RAIL_RECENT_ORGS = 3\n\n/** An account with this many workspaces or fewer sees them all on the rail. */\nexport const RAIL_ALL_WORKSPACES_AT_MOST = 8\n\n/** `users/{uid}` field holding the person's starred screens and records. */\nexport const STARRED_FIELD = 'starred'\n\n/** The most items a person may star. */\nexport const STARRED_MAX = 50\n\n/** `users/{uid}` field holding the screens and records opened most recently. */\nexport const RECENTS_FIELD = 'recents'\n\n/** How many recently opened screens and records are kept. */\nexport const RECENTS_MAX = 30\n\n/** `users/{uid}` field holding the color the person picked for each workspace. */\nexport const WORKSPACE_COLORS_FIELD = 'workspaceColors'\n\n/** The most workspaces a person may pick a color for. */\nexport const WORKSPACE_COLORS_MAX = 100\n\n/** `users/{uid}` field holding the apps' theme, synced across devices. */\nexport const APP_THEME_FIELD = 'appTheme'\n\n/** The longest label a starred or recent item keeps. */\nexport const ACCOUNT_ITEM_LABEL_MAX = 120\n\n/** A picked color: `#` and six hex digits. */\nexport const ACCOUNT_COLOR_PATTERN = '^#[0-9A-Fa-f]{6}$'\n\n/** The color dot a starred item may carry, named so each platform draws it from its theme. */\nexport type StarColor =\n | 'blue'\n | 'purple'\n | 'pink'\n | 'red'\n | 'orange'\n | 'yellow'\n | 'green'\n | 'grey'\n\n/** Every star color, in the order the picker lists them. */\nexport const STAR_COLORS: readonly StarColor[] = [\n 'blue',\n 'purple',\n 'pink',\n 'red',\n 'orange',\n 'yellow',\n 'green',\n 'grey',\n]\n\n/**\n * A screen or a record the person can come back to: what it is, where it\n * belongs, what it was called, and the native screen that opens it.\n */\nexport interface AccountItemRef {\n /**\n * `screen` for a place in the app (a nav manifest row), otherwise the\n * one-box search kind of the record (`page`, `order`, `contact`, …).\n */\n kind: string\n /** The nav row id for a `screen`, otherwise the record's document id. */\n id: string\n /** The workspace it belongs to. */\n orgId: string\n /** The site, for an item that belongs to one. */\n hostId?: string\n /** What it was called when it was starred or opened. */\n label: string\n /** The native screen that opens it. */\n screen: string\n /** That screen's params. */\n params?: Record<string, string>\n}\n\n/** One starred item. */\nexport interface StarredItem extends AccountItemRef {\n color?: StarColor\n starredAtMs: number\n}\n\n/** One recently opened item. */\nexport interface RecentItem extends AccountItemRef {\n openedAtMs: number\n}\n\nexport type AppAppearance = 'system' | 'light' | 'dark'\n\nexport type AppThemeName = 'aglyn' | 'quiet' | 'night' | 'ocean' | 'warm' | 'highContrast'\n\nexport type AppDensity = 'comfortable' | 'compact'\n\nexport type AppTextSize = 'system' | 'small' | 'medium' | 'large' | 'extraLarge'\n\nexport type AppSidebarStyle = 'tinted' | 'plain'\n\n/** The apps' look, saved on the account so it follows the person to every device. */\nexport interface AppThemePrefs {\n /** Light, dark or the system's. */\n appearance: AppAppearance\n /** The theme: rail, sidebar tint, surfaces and accent for light and dark together. */\n theme: AppThemeName\n /** `theme` for the theme's own accent, or a `#RRGGBB` the person picked. */\n accent: string\n density: AppDensity\n /** `system` follows Dynamic Type or the font scale; the rest are the Mac and Windows sizes. */\n textSize: AppTextSize\n sidebarStyle: AppSidebarStyle\n /** The app icon's id: `aglyn`, or one of the alternates the apps ship. */\n appIcon: string\n /** A workspace's own brand color tints the app while you are in it. */\n useWorkspaceBrand: boolean\n}\n\n/** What a person who never changed anything sees. */\nexport const DEFAULT_APP_THEME: AppThemePrefs = {\n appearance: 'system',\n theme: 'aglyn',\n accent: 'theme',\n density: 'comfortable',\n textSize: 'system',\n sidebarStyle: 'tinted',\n appIcon: 'aglyn',\n useWorkspaceBrand: true,\n}\n\n/** Every value each closed theme field accepts, in the order a picker lists them. */\nexport const APP_APPEARANCES: readonly AppAppearance[] = ['system', 'light', 'dark']\nexport const APP_THEME_NAMES: readonly AppThemeName[] = [\n 'aglyn',\n 'quiet',\n 'night',\n 'ocean',\n 'warm',\n 'highContrast',\n]\nexport const APP_DENSITIES: readonly AppDensity[] = ['comfortable', 'compact']\nexport const APP_TEXT_SIZES: readonly AppTextSize[] = [\n 'system',\n 'small',\n 'medium',\n 'large',\n 'extraLarge',\n]\nexport const APP_SIDEBAR_STYLES: readonly AppSidebarStyle[] = ['tinted', 'plain']\n\n/** The longest app icon id. */\nexport const APP_ICON_ID_MAX = 40\n\nfunction field(profile: unknown, name: string): unknown {\n return profile && typeof profile === 'object'\n ? (profile as Record<string, unknown>)[name]\n : undefined\n}\n\nfunction idList(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return [\n ...new Set(\n value.filter((entry): entry is string => typeof entry === 'string' && entry !== ''),\n ),\n ]\n}\n\n/** Whether a value is a picked color, `#` and six hex digits. */\nexport function isAccountColor(value: unknown): value is string {\n return typeof value === 'string' && new RegExp(ACCOUNT_COLOR_PATTERN).test(value)\n}\n\n// ── Pins ────────────────────────────────────────────────────────────────────\n\n/**\n * Every id the person stored as pinned, in their order, without repeats.\n *\n * Anything that is not a list of non-empty strings reads as no pins, so a\n * stray value can never take a switcher down. May hold more than\n * {@link PINNED_ORGS_MAX} for an account that pinned before the cap; read\n * {@link effectivePinnedOrgs} for the pins to show.\n */\nexport function readPinnedOrgs(profile: unknown): string[] {\n return idList(field(profile, PINNED_ORGS_FIELD))\n}\n\n/** The pins in force: the first {@link PINNED_ORGS_MAX} stored, until the person picks. */\nexport function effectivePinnedOrgs(stored: readonly string[]): string[] {\n return idList(stored).slice(0, PINNED_ORGS_MAX)\n}\n\n/** The account pinned more than the cap before there was one, and has to pick which to keep. */\nexport function pinnedOrgsOverCap(stored: readonly string[]): boolean {\n return idList(stored).length > PINNED_ORGS_MAX\n}\n\nexport type PinOrgOutcome = 'pinned' | 'alreadyPinned' | 'full'\n\nexport interface PinOrgResult {\n /** The pins after the request: unchanged unless `outcome` is `pinned`. */\n pinned: string[]\n outcome: PinOrgOutcome\n}\n\n/**\n * Pins a workspace at the end of the list, or says the list is full. A full\n * list is never made room in quietly: the caller asks \"Pinned is full.\n * Replace one?\" and calls {@link replacePinnedOrg} with the answer.\n */\nexport function pinOrg(pinned: readonly string[], orgId: string): PinOrgResult {\n const current = effectivePinnedOrgs(pinned)\n if (!orgId) return { pinned: current, outcome: 'alreadyPinned' }\n if (current.includes(orgId)) return { pinned: current, outcome: 'alreadyPinned' }\n if (current.length >= PINNED_ORGS_MAX) return { pinned: current, outcome: 'full' }\n return { pinned: [...current, orgId], outcome: 'pinned' }\n}\n\n/** The pins without one workspace. */\nexport function unpinOrg(pinned: readonly string[], orgId: string): string[] {\n return idList(pinned).filter((id) => id !== orgId)\n}\n\n/**\n * The pins with `orgId` in the place `replacing` held — the answer to\n * \"Pinned is full. Replace one?\". Unchanged when `replacing` is not pinned\n * or `orgId` already is.\n */\nexport function replacePinnedOrg(\n pinned: readonly string[],\n replacing: string,\n orgId: string,\n): string[] {\n const current = effectivePinnedOrgs(pinned)\n if (!orgId || current.includes(orgId) || !current.includes(replacing)) return current\n return current.map((id) => (id === replacing ? orgId : id))\n}\n\n/**\n * The answer to \"Keep which 5?\": the stored pins the person ticked, in the\n * order they were pinned, never more than the cap.\n */\nexport function keepPinnedOrgs(stored: readonly string[], keep: readonly string[]): string[] {\n const kept = new Set(keep)\n return idList(stored)\n .filter((id) => kept.has(id))\n .slice(0, PINNED_ORGS_MAX)\n}\n\n/** The pins with one moved up (`-1`) or down (`1`) a place; unchanged at an end. */\nexport function movePinnedOrg(pinned: readonly string[], orgId: string, offset: number): string[] {\n const current = effectivePinnedOrgs(pinned)\n const from = current.indexOf(orgId)\n const to = from + (offset < 0 ? -1 : 1)\n if (from === -1 || to < 0 || to >= current.length) return current\n const next = [...current]\n next[from] = next[to]\n next[to] = orgId\n return next\n}\n\n// ── Recent workspaces and the rail ──────────────────────────────────────────\n\n/** The recently opened workspaces as stored, newest first. */\nexport function readRecentOrgs(profile: unknown): string[] {\n return idList(field(profile, RECENT_ORGS_FIELD)).slice(0, RECENT_ORGS_MAX)\n}\n\n/** The recent workspaces after opening one: it moves to the front, the oldest falls off. */\nexport function recordRecentOrg(recent: readonly string[], orgId: string): string[] {\n if (!orgId) return idList(recent).slice(0, RECENT_ORGS_MAX)\n return [orgId, ...idList(recent).filter((id) => id !== orgId)].slice(0, RECENT_ORGS_MAX)\n}\n\n/** What the workspace rail shows. */\nexport interface RailWorkspaces {\n /** Pinned, at the top, in the person's order. */\n pinned: string[]\n /** Below the divider: recently opened and not pinned, at most {@link RAIL_RECENT_ORGS}. */\n recent: string[]\n /** Every other workspace, for an account small enough to list them all. */\n rest: string[]\n /** Whether the last tile is \"All workspaces\", opening the searchable switcher. */\n allTile: boolean\n /** How many workspaces the person belongs to, shown under the All tile. */\n total: number\n}\n\n/**\n * The rail for one account: up to five pinned, a divider, up to three recent,\n * then an \"All workspaces (N)\" tile. An account with\n * {@link RAIL_ALL_WORKSPACES_AT_MOST} workspaces or fewer sees them all and\n * no All tile, since there is nothing to hide. `orgIds` is every membership,\n * in the switcher's order; an id the person no longer belongs to is ignored.\n */\nexport function railWorkspaces(\n orgIds: readonly string[],\n pinned: readonly string[],\n recent: readonly string[],\n): RailWorkspaces {\n const members = idList(orgIds)\n const belongs = new Set(members)\n const pins = effectivePinnedOrgs(pinned).filter((id) => belongs.has(id))\n const pinnedSet = new Set(pins)\n if (members.length <= RAIL_ALL_WORKSPACES_AT_MOST) {\n return {\n pinned: pins,\n recent: [],\n rest: members.filter((id) => !pinnedSet.has(id)),\n allTile: false,\n total: members.length,\n }\n }\n return {\n pinned: pins,\n recent: idList(recent)\n .filter((id) => belongs.has(id) && !pinnedSet.has(id))\n .slice(0, RAIL_RECENT_ORGS),\n rest: [],\n allTile: true,\n total: members.length,\n }\n}\n\n// ── Starred and recent items ────────────────────────────────────────────────\n\n/** One item's identity: the same screen or record in the same place. */\nexport function accountItemKey(\n item: Pick<AccountItemRef, 'kind' | 'id' | 'orgId' | 'hostId'>,\n): string {\n return `${item.kind}:${item.orgId}:${item.hostId ?? ''}:${item.id}`\n}\n\nfunction stringMap(value: unknown): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined\n const out: Record<string, string> = {}\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (typeof entry === 'string') out[key] = entry\n }\n return Object.keys(out).length ? out : undefined\n}\n\nfunction itemRef(value: unknown): AccountItemRef | null {\n if (!value || typeof value !== 'object') return null\n const raw = value as Record<string, unknown>\n const text = (key: string) => (typeof raw[key] === 'string' ? (raw[key] as string) : '')\n const kind = text('kind')\n const id = text('id')\n const orgId = text('orgId')\n const screen = text('screen')\n if (!kind || !id || !orgId || !screen) return null\n const hostId = text('hostId')\n const params = stringMap(raw['params'])\n return {\n kind,\n id,\n orgId,\n ...(hostId ? { hostId } : {}),\n label: text('label').slice(0, ACCOUNT_ITEM_LABEL_MAX),\n screen,\n ...(params ? { params } : {}),\n }\n}\n\nfunction at(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n\nfunction isStarColor(value: unknown): value is StarColor {\n return (STAR_COLORS as readonly unknown[]).includes(value)\n}\n\n/** The starred items as stored, each well formed, without repeats, never more than the cap. */\nexport function readStarred(profile: unknown): StarredItem[] {\n const stored = field(profile, STARRED_FIELD)\n if (!Array.isArray(stored)) return []\n const seen = new Set<string>()\n const out: StarredItem[] = []\n for (const entry of stored) {\n const ref = itemRef(entry)\n if (!ref) continue\n const key = accountItemKey(ref)\n if (seen.has(key)) continue\n seen.add(key)\n const color = (entry as Record<string, unknown>)['color']\n out.push({\n ...ref,\n ...(isStarColor(color) ? { color } : {}),\n starredAtMs: at((entry as Record<string, unknown>)['starredAtMs']),\n })\n }\n return out.slice(0, STARRED_MAX)\n}\n\nexport type StarOutcome = 'starred' | 'unstarred' | 'full'\n\nexport interface StarResult {\n /** The starred items after the request: unchanged when `outcome` is `full`. */\n starred: StarredItem[]\n outcome: StarOutcome\n}\n\n/** Stars an item at the end of the list, or unstars it; a full list says so instead. */\nexport function toggleStarred(\n starred: readonly StarredItem[],\n item: AccountItemRef,\n nowMs: number,\n): StarResult {\n const current = readStarred({ [STARRED_FIELD]: starred })\n const ref = itemRef(item)\n if (!ref) return { starred: current, outcome: 'full' }\n const key = accountItemKey(ref)\n if (current.some((entry) => accountItemKey(entry) === key)) {\n return {\n starred: current.filter((entry) => accountItemKey(entry) !== key),\n outcome: 'unstarred',\n }\n }\n if (current.length >= STARRED_MAX) return { starred: current, outcome: 'full' }\n return { starred: [...current, { ...ref, starredAtMs: nowMs }], outcome: 'starred' }\n}\n\n/** The starred items with one item's color dot set, or cleared with `null`. */\nexport function setStarColor(\n starred: readonly StarredItem[],\n key: string,\n color: StarColor | null,\n): StarredItem[] {\n return readStarred({ [STARRED_FIELD]: starred }).map((entry) => {\n if (accountItemKey(entry) !== key) return entry\n const next: StarredItem = { ...entry }\n delete next.color\n return color && isStarColor(color) ? { ...next, color } : next\n })\n}\n\n/** The recent items as stored, newest first, each well formed, never more than the cap. */\nexport function readRecents(profile: unknown): RecentItem[] {\n const stored = field(profile, RECENTS_FIELD)\n if (!Array.isArray(stored)) return []\n const seen = new Set<string>()\n const out: RecentItem[] = []\n for (const entry of stored) {\n const ref = itemRef(entry)\n if (!ref) continue\n const key = accountItemKey(ref)\n if (seen.has(key)) continue\n seen.add(key)\n out.push({ ...ref, openedAtMs: at((entry as Record<string, unknown>)['openedAtMs']) })\n }\n return out.slice(0, RECENTS_MAX)\n}\n\n/** The recent items after opening one: it moves to the front, the oldest falls off. */\nexport function recordRecent(\n recents: readonly RecentItem[],\n item: AccountItemRef,\n nowMs: number,\n): RecentItem[] {\n const current = readRecents({ [RECENTS_FIELD]: recents })\n const ref = itemRef(item)\n if (!ref) return current\n const key = accountItemKey(ref)\n return [\n { ...ref, openedAtMs: nowMs },\n ...current.filter((entry) => accountItemKey(entry) !== key),\n ].slice(0, RECENTS_MAX)\n}\n\n// ── Workspace colors ────────────────────────────────────────────────────────\n\n/** The colors the person picked, by workspace id, the valid ones only. */\nexport function readWorkspaceColors(profile: unknown): Record<string, string> {\n const stored = field(profile, WORKSPACE_COLORS_FIELD)\n if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return {}\n const out: Record<string, string> = {}\n for (const [orgId, color] of Object.entries(stored as Record<string, unknown>)) {\n if (orgId && isAccountColor(color)) out[orgId] = color\n if (Object.keys(out).length >= WORKSPACE_COLORS_MAX) break\n }\n return out\n}\n\nexport type WorkspaceColorSource = 'picked' | 'brand' | 'logo' | 'none'\n\nexport interface WorkspaceColor {\n /** `#RRGGBB`, or null when nothing gives the workspace a color. */\n color: string | null\n source: WorkspaceColorSource\n /** Whether the app is tinted with it while you are in the workspace. */\n tintsApp: boolean\n}\n\n/**\n * A workspace's color: the one the person picked, else the workspace's brand\n * color (`brandingProfile.primaryColor`), else a color taken from its logo\n * (Zach, 2026-10-18). The rail tile always shows it; the app is tinted with\n * it while you are in the workspace unless the person turned \"Use each\n * workspace's brand\" off, which stops the brand and logo colors tinting the\n * app (a picked color still does: the person chose it).\n */\nexport function resolveWorkspaceColor(\n picked: unknown,\n brandColor: unknown,\n logoColor: unknown,\n useWorkspaceBrand: boolean,\n): WorkspaceColor {\n if (isAccountColor(picked)) return { color: picked, source: 'picked', tintsApp: true }\n if (isAccountColor(brandColor)) {\n return { color: brandColor, source: 'brand', tintsApp: useWorkspaceBrand }\n }\n if (isAccountColor(logoColor)) {\n return { color: logoColor, source: 'logo', tintsApp: useWorkspaceBrand }\n }\n return { color: null, source: 'none', tintsApp: false }\n}\n\n// ── Theme ───────────────────────────────────────────────────────────────────\n\nfunction oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {\n return (allowed as readonly unknown[]).includes(value) ? (value as T) : fallback\n}\n\n/** The synced theme, every field filled: a missing or unknown value reads as the default. */\nexport function readAppTheme(profile: unknown): AppThemePrefs {\n const stored = field(profile, APP_THEME_FIELD)\n const raw =\n stored && typeof stored === 'object' && !Array.isArray(stored)\n ? (stored as Record<string, unknown>)\n : {}\n const accent = raw['accent']\n const appIcon = raw['appIcon']\n return {\n appearance: oneOf(raw['appearance'], APP_APPEARANCES, DEFAULT_APP_THEME.appearance),\n theme: oneOf(raw['theme'], APP_THEME_NAMES, DEFAULT_APP_THEME.theme),\n accent: accent === 'theme' || isAccountColor(accent) ? accent : DEFAULT_APP_THEME.accent,\n density: oneOf(raw['density'], APP_DENSITIES, DEFAULT_APP_THEME.density),\n textSize: oneOf(raw['textSize'], APP_TEXT_SIZES, DEFAULT_APP_THEME.textSize),\n sidebarStyle: oneOf(raw['sidebarStyle'], APP_SIDEBAR_STYLES, DEFAULT_APP_THEME.sidebarStyle),\n appIcon:\n typeof appIcon === 'string' && appIcon !== '' && appIcon.length <= APP_ICON_ID_MAX\n ? appIcon\n : DEFAULT_APP_THEME.appIcon,\n useWorkspaceBrand:\n typeof raw['useWorkspaceBrand'] === 'boolean'\n ? (raw['useWorkspaceBrand'] as boolean)\n : DEFAULT_APP_THEME.useWorkspaceBrand,\n }\n}\n"],"names":["PINNED_ORGS_FIELD","PINNED_ORGS_MAX","RECENT_ORGS_FIELD","RECENT_ORGS_MAX","RAIL_RECENT_ORGS","RAIL_ALL_WORKSPACES_AT_MOST","STARRED_FIELD","STARRED_MAX","RECENTS_FIELD","RECENTS_MAX","WORKSPACE_COLORS_FIELD","WORKSPACE_COLORS_MAX","APP_THEME_FIELD","ACCOUNT_ITEM_LABEL_MAX","ACCOUNT_COLOR_PATTERN","STAR_COLORS","DEFAULT_APP_THEME","appearance","theme","accent","density","textSize","sidebarStyle","appIcon","useWorkspaceBrand","APP_APPEARANCES","APP_THEME_NAMES","APP_DENSITIES","APP_TEXT_SIZES","APP_SIDEBAR_STYLES","APP_ICON_ID_MAX","field","profile","name","undefined","idList","value","Array","isArray","Set","filter","entry","isAccountColor","RegExp","test","readPinnedOrgs","effectivePinnedOrgs","stored","slice","pinnedOrgsOverCap","length","pinOrg","pinned","orgId","current","outcome","includes","unpinOrg","id","replacePinnedOrg","replacing","map","keepPinnedOrgs","keep","kept","has","movePinnedOrg","offset","from","indexOf","to","next","readRecentOrgs","recordRecentOrg","recent","railWorkspaces","orgIds","members","belongs","pins","pinnedSet","rest","allTile","total","accountItemKey","item","kind","hostId","stringMap","out","key","Object","entries","keys","itemRef","raw","text","screen","params","label","at","Number","isFinite","isStarColor","readStarred","seen","ref","add","color","push","starredAtMs","toggleStarred","starred","nowMs","some","setStarColor","readRecents","openedAtMs","recordRecent","recents","readWorkspaceColors","resolveWorkspaceColor","picked","brandColor","logoColor","source","tintsApp","oneOf","allowed","fallback","readAppTheme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/account-preferences.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 PREFERENCES A PERSON KEEPS ON THEIR ACCOUNT (AGL-3943).\n *\n * One documented shape on `users/{uid}`, read and written the same way by\n * the console and both native apps (docs/mobile/native-navigation.md):\n *\n * | field | shape | cap |\n * | -- | -- | -- |\n * | `pinnedOrgs` | workspace ids, in the order the person keeps them | 5 |\n * | `pinnedHosts` | {@link PinnedHost}s, in the order they were pinned | 5 a workspace, 100 in all |\n * | `recentOrgs` | workspace ids, most recently opened first | 8 |\n * | `starred` | {@link StarredItem}s, in the order they were starred | 50 |\n * | `recents` | {@link RecentItem}s, most recently opened first | 30 |\n * | `workspaceColors` | workspace id → `#RRGGBB` the person picked | 100 |\n * | `appTheme` | {@link AppThemePrefs}, the apps' look, synced | — |\n *\n * The caps are held twice: here, by every writer, and by the Firestore rules,\n * which refuse a write that grows a list past its cap. A list that is ALREADY\n * past it (pins made before the cap, Zach 2026-10-18) may only shrink, so a\n * person with seven pins can still unpin one; the console asks them once\n * which five to keep, and until they answer the first five are the pins.\n *\n * A per-device override of the theme (\"this device only\") is the device's\n * own, kept in local storage on each platform, and never written here.\n *\n * NO IMPORTS, on purpose: the native apps read these constants, types and\n * the answers of these functions through the generated contracts, so it must\n * stay a plain module.\n */\n\n/** `users/{uid}` field holding the ids of the workspaces the person pinned. */\nexport const PINNED_ORGS_FIELD = 'pinnedOrgs'\n\n/** The most workspaces a person may pin, everywhere, the console included. */\nexport const PINNED_ORGS_MAX = 5\n\n/** `users/{uid}` field holding the workspaces opened most recently, newest first. */\nexport const RECENT_ORGS_FIELD = 'recentOrgs'\n\n/** How many recently opened workspaces are kept. */\nexport const RECENT_ORGS_MAX = 8\n\n/** How many recent workspaces the rail shows below the pins. */\nexport const RAIL_RECENT_ORGS = 3\n\n/** An account with this many workspaces or fewer sees them all on the rail. */\nexport const RAIL_ALL_WORKSPACES_AT_MOST = 8\n\n/** `users/{uid}` field holding the person's starred screens and records. */\nexport const STARRED_FIELD = 'starred'\n\n/** The most items a person may star. */\nexport const STARRED_MAX = 50\n\n/** `users/{uid}` field holding the screens and records opened most recently. */\nexport const RECENTS_FIELD = 'recents'\n\n/** How many recently opened screens and records are kept. */\nexport const RECENTS_MAX = 30\n\n/** `users/{uid}` field holding the color the person picked for each workspace. */\nexport const WORKSPACE_COLORS_FIELD = 'workspaceColors'\n\n/** The most workspaces a person may pick a color for. */\nexport const WORKSPACE_COLORS_MAX = 100\n\n/** `users/{uid}` field holding the apps' theme, synced across devices. */\nexport const APP_THEME_FIELD = 'appTheme'\n\n/** The longest label a starred or recent item keeps. */\nexport const ACCOUNT_ITEM_LABEL_MAX = 120\n\n/** A picked color: `#` and six hex digits. */\nexport const ACCOUNT_COLOR_PATTERN = '^#[0-9A-Fa-f]{6}$'\n\n/** The color dot a starred item may carry, named so each platform draws it from its theme. */\nexport type StarColor =\n | 'blue'\n | 'purple'\n | 'pink'\n | 'red'\n | 'orange'\n | 'yellow'\n | 'green'\n | 'grey'\n\n/** Every star color, in the order the picker lists them. */\nexport const STAR_COLORS: readonly StarColor[] = [\n 'blue',\n 'purple',\n 'pink',\n 'red',\n 'orange',\n 'yellow',\n 'green',\n 'grey',\n]\n\n/**\n * A screen or a record the person can come back to: what it is, where it\n * belongs, what it was called, and the native screen that opens it.\n */\nexport interface AccountItemRef {\n /**\n * `screen` for a place in the app (a nav manifest row), otherwise the\n * one-box search kind of the record (`page`, `order`, `contact`, …).\n */\n kind: string\n /** The nav row id for a `screen`, otherwise the record's document id. */\n id: string\n /** The workspace it belongs to. */\n orgId: string\n /** The site, for an item that belongs to one. */\n hostId?: string\n /** What it was called when it was starred or opened. */\n label: string\n /** The native screen that opens it. */\n screen: string\n /** That screen's params. */\n params?: Record<string, string>\n}\n\n/** One starred item. */\nexport interface StarredItem extends AccountItemRef {\n color?: StarColor\n starredAtMs: number\n}\n\n/** One recently opened item. */\nexport interface RecentItem extends AccountItemRef {\n openedAtMs: number\n}\n\nexport type AppAppearance = 'system' | 'light' | 'dark'\n\nexport type AppThemeName = 'aglyn' | 'quiet' | 'night' | 'ocean' | 'warm' | 'highContrast'\n\nexport type AppDensity = 'comfortable' | 'compact'\n\nexport type AppTextSize = 'system' | 'small' | 'medium' | 'large' | 'extraLarge'\n\nexport type AppSidebarStyle = 'tinted' | 'plain'\n\n/** The apps' look, saved on the account so it follows the person to every device. */\nexport interface AppThemePrefs {\n /** Light, dark or the system's. */\n appearance: AppAppearance\n /** The theme: rail, sidebar tint, surfaces and accent for light and dark together. */\n theme: AppThemeName\n /** `theme` for the theme's own accent, or a `#RRGGBB` the person picked. */\n accent: string\n density: AppDensity\n /** `system` follows Dynamic Type or the font scale; the rest are the Mac and Windows sizes. */\n textSize: AppTextSize\n sidebarStyle: AppSidebarStyle\n /** The app icon's id: `aglyn`, or one of the alternates the apps ship. */\n appIcon: string\n /** A workspace's own brand color tints the app while you are in it. */\n useWorkspaceBrand: boolean\n}\n\n/** What a person who never changed anything sees. */\nexport const DEFAULT_APP_THEME: AppThemePrefs = {\n appearance: 'system',\n theme: 'aglyn',\n accent: 'theme',\n density: 'comfortable',\n textSize: 'system',\n sidebarStyle: 'tinted',\n appIcon: 'aglyn',\n useWorkspaceBrand: true,\n}\n\n/** Every value each closed theme field accepts, in the order a picker lists them. */\nexport const APP_APPEARANCES: readonly AppAppearance[] = ['system', 'light', 'dark']\nexport const APP_THEME_NAMES: readonly AppThemeName[] = [\n 'aglyn',\n 'quiet',\n 'night',\n 'ocean',\n 'warm',\n 'highContrast',\n]\nexport const APP_DENSITIES: readonly AppDensity[] = ['comfortable', 'compact']\nexport const APP_TEXT_SIZES: readonly AppTextSize[] = [\n 'system',\n 'small',\n 'medium',\n 'large',\n 'extraLarge',\n]\nexport const APP_SIDEBAR_STYLES: readonly AppSidebarStyle[] = ['tinted', 'plain']\n\n/** The longest app icon id. */\nexport const APP_ICON_ID_MAX = 40\n\nfunction field(profile: unknown, name: string): unknown {\n return profile && typeof profile === 'object'\n ? (profile as Record<string, unknown>)[name]\n : undefined\n}\n\nfunction idList(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return [\n ...new Set(\n value.filter((entry): entry is string => typeof entry === 'string' && entry !== ''),\n ),\n ]\n}\n\n/** Whether a value is a picked color, `#` and six hex digits. */\nexport function isAccountColor(value: unknown): value is string {\n return typeof value === 'string' && new RegExp(ACCOUNT_COLOR_PATTERN).test(value)\n}\n\n// ── Pins ────────────────────────────────────────────────────────────────────\n\n/**\n * Every id the person stored as pinned, in their order, without repeats.\n *\n * Anything that is not a list of non-empty strings reads as no pins, so a\n * stray value can never take a switcher down. May hold more than\n * {@link PINNED_ORGS_MAX} for an account that pinned before the cap; read\n * {@link effectivePinnedOrgs} for the pins to show.\n */\nexport function readPinnedOrgs(profile: unknown): string[] {\n return idList(field(profile, PINNED_ORGS_FIELD))\n}\n\n/** The pins in force: the first {@link PINNED_ORGS_MAX} stored, until the person picks. */\nexport function effectivePinnedOrgs(stored: readonly string[]): string[] {\n return idList(stored).slice(0, PINNED_ORGS_MAX)\n}\n\n/** The account pinned more than the cap before there was one, and has to pick which to keep. */\nexport function pinnedOrgsOverCap(stored: readonly string[]): boolean {\n return idList(stored).length > PINNED_ORGS_MAX\n}\n\nexport type PinOrgOutcome = 'pinned' | 'alreadyPinned' | 'full'\n\nexport interface PinOrgResult {\n /** The pins after the request: unchanged unless `outcome` is `pinned`. */\n pinned: string[]\n outcome: PinOrgOutcome\n}\n\n/**\n * Pins a workspace at the end of the list, or says the list is full. A full\n * list is never made room in quietly: the caller asks \"Pinned is full.\n * Replace one?\" and calls {@link replacePinnedOrg} with the answer.\n */\nexport function pinOrg(pinned: readonly string[], orgId: string): PinOrgResult {\n const current = effectivePinnedOrgs(pinned)\n if (!orgId) return { pinned: current, outcome: 'alreadyPinned' }\n if (current.includes(orgId)) return { pinned: current, outcome: 'alreadyPinned' }\n if (current.length >= PINNED_ORGS_MAX) return { pinned: current, outcome: 'full' }\n return { pinned: [...current, orgId], outcome: 'pinned' }\n}\n\n/** The pins without one workspace. */\nexport function unpinOrg(pinned: readonly string[], orgId: string): string[] {\n return idList(pinned).filter((id) => id !== orgId)\n}\n\n/**\n * The pins with `orgId` in the place `replacing` held — the answer to\n * \"Pinned is full. Replace one?\". Unchanged when `replacing` is not pinned\n * or `orgId` already is.\n */\nexport function replacePinnedOrg(\n pinned: readonly string[],\n replacing: string,\n orgId: string,\n): string[] {\n const current = effectivePinnedOrgs(pinned)\n if (!orgId || current.includes(orgId) || !current.includes(replacing)) return current\n return current.map((id) => (id === replacing ? orgId : id))\n}\n\n/**\n * The answer to \"Keep which 5?\": the stored pins the person ticked, in the\n * order they were pinned, never more than the cap.\n */\nexport function keepPinnedOrgs(stored: readonly string[], keep: readonly string[]): string[] {\n const kept = new Set(keep)\n return idList(stored)\n .filter((id) => kept.has(id))\n .slice(0, PINNED_ORGS_MAX)\n}\n\n/** The pins with one moved up (`-1`) or down (`1`) a place; unchanged at an end. */\nexport function movePinnedOrg(pinned: readonly string[], orgId: string, offset: number): string[] {\n const current = effectivePinnedOrgs(pinned)\n const from = current.indexOf(orgId)\n const to = from + (offset < 0 ? -1 : 1)\n if (from === -1 || to < 0 || to >= current.length) return current\n const next = [...current]\n next[from] = next[to]\n next[to] = orgId\n return next\n}\n\n// ── Site pins ───────────────────────────────────────────────────────────────\n\n/** `users/{uid}` field holding the sites the person pinned, each with its workspace (AGL-4005). */\nexport const PINNED_HOSTS_FIELD = 'pinnedHosts'\n\n/** The most sites a person may pin in one workspace: five, as for workspaces. */\nexport const PINNED_HOSTS_PER_WORKSPACE = 5\n\n/**\n * The most site pins the account keeps across every workspace, the bound the\n * Firestore rules hold. Nobody meets it by pinning: it takes twenty\n * workspaces with five each. Past it, the oldest pins of OTHER workspaces\n * give way (see {@link pinHost}).\n */\nexport const PINNED_HOSTS_MAX = 100\n\n/**\n * One pinned site and the workspace it belongs to. The workspace rides with\n * the pin so a surface that holds only one workspace's sites (the site\n * switcher's window) can tell its own pins apart without reading every site.\n */\nexport interface PinnedHost {\n orgId: string\n hostId: string\n}\n\nfunction pinnedHostList(value: unknown): PinnedHost[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n const pins: PinnedHost[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== 'object') continue\n const { orgId, hostId } = entry as Record<string, unknown>\n if (typeof orgId !== 'string' || !orgId) continue\n if (typeof hostId !== 'string' || !hostId || seen.has(hostId)) continue\n seen.add(hostId)\n pins.push({ orgId, hostId })\n }\n return pins\n}\n\n/**\n * Every site pin the person stored, in the order pinned, without repeats.\n * Anything that is not a `{ orgId, hostId }` reads as no pin, so a stray\n * value can never take the sites list down.\n */\nexport function readPinnedHosts(profile: unknown): PinnedHost[] {\n return pinnedHostList(field(profile, PINNED_HOSTS_FIELD))\n}\n\n/** One workspace's pinned site ids, in the order pinned, at most five. */\nexport function workspacePinnedHosts(stored: readonly PinnedHost[], orgId: string): string[] {\n return pinnedHostList(stored)\n .filter((pin) => pin.orgId === orgId)\n .map((pin) => pin.hostId)\n .slice(0, PINNED_HOSTS_PER_WORKSPACE)\n}\n\nexport interface PinHostResult {\n /** Every stored pin after the request: unchanged unless `outcome` is `pinned`. */\n pinnedHosts: PinnedHost[]\n outcome: PinOrgOutcome\n}\n\n/**\n * Pins a site at the end of its workspace's pins, or says that workspace\n * already has five. As with workspaces, a full list is never made room in\n * quietly: the caller asks \"Pinned is full. Replace one?\" and calls\n * {@link replacePinnedHost} with the answer. Only the account-wide bound\n * ({@link PINNED_HOSTS_MAX}) makes room, and only from other workspaces.\n */\nexport function pinHost(stored: readonly PinnedHost[], pin: PinnedHost): PinHostResult {\n const current = pinnedHostList(stored)\n if (!pin.orgId || !pin.hostId) return { pinnedHosts: current, outcome: 'alreadyPinned' }\n const inWorkspace = workspacePinnedHosts(current, pin.orgId)\n if (inWorkspace.includes(pin.hostId)) return { pinnedHosts: current, outcome: 'alreadyPinned' }\n if (inWorkspace.length >= PINNED_HOSTS_PER_WORKSPACE) {\n return { pinnedHosts: current, outcome: 'full' }\n }\n // A site pinned under the workspace it has since moved out of moves with it.\n const next = [\n ...current.filter((entry) => entry.hostId !== pin.hostId),\n { orgId: pin.orgId, hostId: pin.hostId },\n ]\n while (next.length > PINNED_HOSTS_MAX) {\n const oldest = next.findIndex((entry) => entry.orgId !== pin.orgId)\n if (oldest === -1) break\n next.splice(oldest, 1)\n }\n return { pinnedHosts: next, outcome: 'pinned' }\n}\n\n/** The stored pins without one site. */\nexport function unpinHost(stored: readonly PinnedHost[], hostId: string): PinnedHost[] {\n return pinnedHostList(stored).filter((entry) => entry.hostId !== hostId)\n}\n\n/**\n * The stored pins with `pin` in the place `replacing` held in the same\n * workspace: the answer to \"Pinned is full. Replace one?\". Unchanged when\n * `replacing` is not one of that workspace's pins or `pin` already is.\n */\nexport function replacePinnedHost(\n stored: readonly PinnedHost[],\n replacing: string,\n pin: PinnedHost,\n): PinnedHost[] {\n const current = pinnedHostList(stored)\n const inWorkspace = workspacePinnedHosts(current, pin.orgId)\n if (!pin.hostId || inWorkspace.includes(pin.hostId) || !inWorkspace.includes(replacing)) {\n return current\n }\n return current\n .filter((entry) => entry.hostId !== pin.hostId)\n .map((entry) =>\n entry.hostId === replacing ? { orgId: pin.orgId, hostId: pin.hostId } : entry,\n )\n}\n\n// ── Recent workspaces and the rail ──────────────────────────────────────────\n\n/** The recently opened workspaces as stored, newest first. */\nexport function readRecentOrgs(profile: unknown): string[] {\n return idList(field(profile, RECENT_ORGS_FIELD)).slice(0, RECENT_ORGS_MAX)\n}\n\n/** The recent workspaces after opening one: it moves to the front, the oldest falls off. */\nexport function recordRecentOrg(recent: readonly string[], orgId: string): string[] {\n if (!orgId) return idList(recent).slice(0, RECENT_ORGS_MAX)\n return [orgId, ...idList(recent).filter((id) => id !== orgId)].slice(0, RECENT_ORGS_MAX)\n}\n\n/** What the workspace rail shows. */\nexport interface RailWorkspaces {\n /** Pinned, at the top, in the person's order. */\n pinned: string[]\n /** Below the divider: recently opened and not pinned, at most {@link RAIL_RECENT_ORGS}. */\n recent: string[]\n /** Every other workspace, for an account small enough to list them all. */\n rest: string[]\n /** Whether the last tile is \"All workspaces\", opening the searchable switcher. */\n allTile: boolean\n /** How many workspaces the person belongs to, shown under the All tile. */\n total: number\n}\n\n/**\n * The rail for one account: up to five pinned, a divider, up to three recent,\n * then an \"All workspaces (N)\" tile. An account with\n * {@link RAIL_ALL_WORKSPACES_AT_MOST} workspaces or fewer sees them all and\n * no All tile, since there is nothing to hide. `orgIds` is every membership,\n * in the switcher's order; an id the person no longer belongs to is ignored.\n */\nexport function railWorkspaces(\n orgIds: readonly string[],\n pinned: readonly string[],\n recent: readonly string[],\n): RailWorkspaces {\n const members = idList(orgIds)\n const belongs = new Set(members)\n const pins = effectivePinnedOrgs(pinned).filter((id) => belongs.has(id))\n const pinnedSet = new Set(pins)\n if (members.length <= RAIL_ALL_WORKSPACES_AT_MOST) {\n return {\n pinned: pins,\n recent: [],\n rest: members.filter((id) => !pinnedSet.has(id)),\n allTile: false,\n total: members.length,\n }\n }\n return {\n pinned: pins,\n recent: idList(recent)\n .filter((id) => belongs.has(id) && !pinnedSet.has(id))\n .slice(0, RAIL_RECENT_ORGS),\n rest: [],\n allTile: true,\n total: members.length,\n }\n}\n\n// ── Starred and recent items ────────────────────────────────────────────────\n\n/** One item's identity: the same screen or record in the same place. */\nexport function accountItemKey(\n item: Pick<AccountItemRef, 'kind' | 'id' | 'orgId' | 'hostId'>,\n): string {\n return `${item.kind}:${item.orgId}:${item.hostId ?? ''}:${item.id}`\n}\n\nfunction stringMap(value: unknown): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined\n const out: Record<string, string> = {}\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (typeof entry === 'string') out[key] = entry\n }\n return Object.keys(out).length ? out : undefined\n}\n\nfunction itemRef(value: unknown): AccountItemRef | null {\n if (!value || typeof value !== 'object') return null\n const raw = value as Record<string, unknown>\n const text = (key: string) => (typeof raw[key] === 'string' ? (raw[key] as string) : '')\n const kind = text('kind')\n const id = text('id')\n const orgId = text('orgId')\n const screen = text('screen')\n if (!kind || !id || !orgId || !screen) return null\n const hostId = text('hostId')\n const params = stringMap(raw['params'])\n return {\n kind,\n id,\n orgId,\n ...(hostId ? { hostId } : {}),\n label: text('label').slice(0, ACCOUNT_ITEM_LABEL_MAX),\n screen,\n ...(params ? { params } : {}),\n }\n}\n\nfunction at(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n\nfunction isStarColor(value: unknown): value is StarColor {\n return (STAR_COLORS as readonly unknown[]).includes(value)\n}\n\n/** The starred items as stored, each well formed, without repeats, never more than the cap. */\nexport function readStarred(profile: unknown): StarredItem[] {\n const stored = field(profile, STARRED_FIELD)\n if (!Array.isArray(stored)) return []\n const seen = new Set<string>()\n const out: StarredItem[] = []\n for (const entry of stored) {\n const ref = itemRef(entry)\n if (!ref) continue\n const key = accountItemKey(ref)\n if (seen.has(key)) continue\n seen.add(key)\n const color = (entry as Record<string, unknown>)['color']\n out.push({\n ...ref,\n ...(isStarColor(color) ? { color } : {}),\n starredAtMs: at((entry as Record<string, unknown>)['starredAtMs']),\n })\n }\n return out.slice(0, STARRED_MAX)\n}\n\nexport type StarOutcome = 'starred' | 'unstarred' | 'full'\n\nexport interface StarResult {\n /** The starred items after the request: unchanged when `outcome` is `full`. */\n starred: StarredItem[]\n outcome: StarOutcome\n}\n\n/** Stars an item at the end of the list, or unstars it; a full list says so instead. */\nexport function toggleStarred(\n starred: readonly StarredItem[],\n item: AccountItemRef,\n nowMs: number,\n): StarResult {\n const current = readStarred({ [STARRED_FIELD]: starred })\n const ref = itemRef(item)\n if (!ref) return { starred: current, outcome: 'full' }\n const key = accountItemKey(ref)\n if (current.some((entry) => accountItemKey(entry) === key)) {\n return {\n starred: current.filter((entry) => accountItemKey(entry) !== key),\n outcome: 'unstarred',\n }\n }\n if (current.length >= STARRED_MAX) return { starred: current, outcome: 'full' }\n return { starred: [...current, { ...ref, starredAtMs: nowMs }], outcome: 'starred' }\n}\n\n/** The starred items with one item's color dot set, or cleared with `null`. */\nexport function setStarColor(\n starred: readonly StarredItem[],\n key: string,\n color: StarColor | null,\n): StarredItem[] {\n return readStarred({ [STARRED_FIELD]: starred }).map((entry) => {\n if (accountItemKey(entry) !== key) return entry\n const next: StarredItem = { ...entry }\n delete next.color\n return color && isStarColor(color) ? { ...next, color } : next\n })\n}\n\n/** The recent items as stored, newest first, each well formed, never more than the cap. */\nexport function readRecents(profile: unknown): RecentItem[] {\n const stored = field(profile, RECENTS_FIELD)\n if (!Array.isArray(stored)) return []\n const seen = new Set<string>()\n const out: RecentItem[] = []\n for (const entry of stored) {\n const ref = itemRef(entry)\n if (!ref) continue\n const key = accountItemKey(ref)\n if (seen.has(key)) continue\n seen.add(key)\n out.push({ ...ref, openedAtMs: at((entry as Record<string, unknown>)['openedAtMs']) })\n }\n return out.slice(0, RECENTS_MAX)\n}\n\n/** The recent items after opening one: it moves to the front, the oldest falls off. */\nexport function recordRecent(\n recents: readonly RecentItem[],\n item: AccountItemRef,\n nowMs: number,\n): RecentItem[] {\n const current = readRecents({ [RECENTS_FIELD]: recents })\n const ref = itemRef(item)\n if (!ref) return current\n const key = accountItemKey(ref)\n return [\n { ...ref, openedAtMs: nowMs },\n ...current.filter((entry) => accountItemKey(entry) !== key),\n ].slice(0, RECENTS_MAX)\n}\n\n// ── Workspace colors ────────────────────────────────────────────────────────\n\n/** The colors the person picked, by workspace id, the valid ones only. */\nexport function readWorkspaceColors(profile: unknown): Record<string, string> {\n const stored = field(profile, WORKSPACE_COLORS_FIELD)\n if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return {}\n const out: Record<string, string> = {}\n for (const [orgId, color] of Object.entries(stored as Record<string, unknown>)) {\n if (orgId && isAccountColor(color)) out[orgId] = color\n if (Object.keys(out).length >= WORKSPACE_COLORS_MAX) break\n }\n return out\n}\n\nexport type WorkspaceColorSource = 'picked' | 'brand' | 'logo' | 'none'\n\nexport interface WorkspaceColor {\n /** `#RRGGBB`, or null when nothing gives the workspace a color. */\n color: string | null\n source: WorkspaceColorSource\n /** Whether the app is tinted with it while you are in the workspace. */\n tintsApp: boolean\n}\n\n/**\n * A workspace's color: the one the person picked, else the workspace's brand\n * color (`brandingProfile.primaryColor`), else a color taken from its logo\n * (Zach, 2026-10-18). The rail tile always shows it; the app is tinted with\n * it while you are in the workspace unless the person turned \"Use each\n * workspace's brand\" off, which stops the brand and logo colors tinting the\n * app (a picked color still does: the person chose it).\n */\nexport function resolveWorkspaceColor(\n picked: unknown,\n brandColor: unknown,\n logoColor: unknown,\n useWorkspaceBrand: boolean,\n): WorkspaceColor {\n if (isAccountColor(picked)) return { color: picked, source: 'picked', tintsApp: true }\n if (isAccountColor(brandColor)) {\n return { color: brandColor, source: 'brand', tintsApp: useWorkspaceBrand }\n }\n if (isAccountColor(logoColor)) {\n return { color: logoColor, source: 'logo', tintsApp: useWorkspaceBrand }\n }\n return { color: null, source: 'none', tintsApp: false }\n}\n\n// ── Theme ───────────────────────────────────────────────────────────────────\n\nfunction oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {\n return (allowed as readonly unknown[]).includes(value) ? (value as T) : fallback\n}\n\n/** The synced theme, every field filled: a missing or unknown value reads as the default. */\nexport function readAppTheme(profile: unknown): AppThemePrefs {\n const stored = field(profile, APP_THEME_FIELD)\n const raw =\n stored && typeof stored === 'object' && !Array.isArray(stored)\n ? (stored as Record<string, unknown>)\n : {}\n const accent = raw['accent']\n const appIcon = raw['appIcon']\n return {\n appearance: oneOf(raw['appearance'], APP_APPEARANCES, DEFAULT_APP_THEME.appearance),\n theme: oneOf(raw['theme'], APP_THEME_NAMES, DEFAULT_APP_THEME.theme),\n accent: accent === 'theme' || isAccountColor(accent) ? accent : DEFAULT_APP_THEME.accent,\n density: oneOf(raw['density'], APP_DENSITIES, DEFAULT_APP_THEME.density),\n textSize: oneOf(raw['textSize'], APP_TEXT_SIZES, DEFAULT_APP_THEME.textSize),\n sidebarStyle: oneOf(raw['sidebarStyle'], APP_SIDEBAR_STYLES, DEFAULT_APP_THEME.sidebarStyle),\n appIcon:\n typeof appIcon === 'string' && appIcon !== '' && appIcon.length <= APP_ICON_ID_MAX\n ? appIcon\n : DEFAULT_APP_THEME.appIcon,\n useWorkspaceBrand:\n typeof raw['useWorkspaceBrand'] === 'boolean'\n ? (raw['useWorkspaceBrand'] as boolean)\n : DEFAULT_APP_THEME.useWorkspaceBrand,\n }\n}\n"],"names":["PINNED_ORGS_FIELD","PINNED_ORGS_MAX","RECENT_ORGS_FIELD","RECENT_ORGS_MAX","RAIL_RECENT_ORGS","RAIL_ALL_WORKSPACES_AT_MOST","STARRED_FIELD","STARRED_MAX","RECENTS_FIELD","RECENTS_MAX","WORKSPACE_COLORS_FIELD","WORKSPACE_COLORS_MAX","APP_THEME_FIELD","ACCOUNT_ITEM_LABEL_MAX","ACCOUNT_COLOR_PATTERN","STAR_COLORS","DEFAULT_APP_THEME","appearance","theme","accent","density","textSize","sidebarStyle","appIcon","useWorkspaceBrand","APP_APPEARANCES","APP_THEME_NAMES","APP_DENSITIES","APP_TEXT_SIZES","APP_SIDEBAR_STYLES","APP_ICON_ID_MAX","field","profile","name","undefined","idList","value","Array","isArray","Set","filter","entry","isAccountColor","RegExp","test","readPinnedOrgs","effectivePinnedOrgs","stored","slice","pinnedOrgsOverCap","length","pinOrg","pinned","orgId","current","outcome","includes","unpinOrg","id","replacePinnedOrg","replacing","map","keepPinnedOrgs","keep","kept","has","movePinnedOrg","offset","from","indexOf","to","next","PINNED_HOSTS_FIELD","PINNED_HOSTS_PER_WORKSPACE","PINNED_HOSTS_MAX","pinnedHostList","seen","pins","hostId","add","push","readPinnedHosts","workspacePinnedHosts","pin","pinHost","pinnedHosts","inWorkspace","oldest","findIndex","splice","unpinHost","replacePinnedHost","readRecentOrgs","recordRecentOrg","recent","railWorkspaces","orgIds","members","belongs","pinnedSet","rest","allTile","total","accountItemKey","item","kind","stringMap","out","key","Object","entries","keys","itemRef","raw","text","screen","params","label","at","Number","isFinite","isStarColor","readStarred","ref","color","starredAtMs","toggleStarred","starred","nowMs","some","setStarColor","readRecents","openedAtMs","recordRecent","recents","readWorkspaceColors","resolveWorkspaceColor","picked","brandColor","logoColor","source","tintsApp","oneOf","allowed","fallback","readAppTheme"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GAED,6EAA6E,GAC7E,OAAO,MAAMA,oBAAoB,aAAY;AAE7C,4EAA4E,GAC5E,OAAO,MAAMC,kBAAkB,EAAC;AAEhC,mFAAmF,GACnF,OAAO,MAAMC,oBAAoB,aAAY;AAE7C,kDAAkD,GAClD,OAAO,MAAMC,kBAAkB,EAAC;AAEhC,8DAA8D,GAC9D,OAAO,MAAMC,mBAAmB,EAAC;AAEjC,6EAA6E,GAC7E,OAAO,MAAMC,8BAA8B,EAAC;AAE5C,0EAA0E,GAC1E,OAAO,MAAMC,gBAAgB,UAAS;AAEtC,sCAAsC,GACtC,OAAO,MAAMC,cAAc,GAAE;AAE7B,8EAA8E,GAC9E,OAAO,MAAMC,gBAAgB,UAAS;AAEtC,2DAA2D,GAC3D,OAAO,MAAMC,cAAc,GAAE;AAE7B,gFAAgF,GAChF,OAAO,MAAMC,yBAAyB,kBAAiB;AAEvD,uDAAuD,GACvD,OAAO,MAAMC,uBAAuB,IAAG;AAEvC,wEAAwE,GACxE,OAAO,MAAMC,kBAAkB,WAAU;AAEzC,sDAAsD,GACtD,OAAO,MAAMC,yBAAyB,IAAG;AAEzC,4CAA4C,GAC5C,OAAO,MAAMC,wBAAwB,oBAAmB;AAaxD,0DAA0D,GAC1D,OAAO,MAAMC,cAAoC;IAC/C;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAiED,mDAAmD,GACnD,OAAO,MAAMC,oBAAmC;IAC9CC,YAAY;IACZC,OAAO;IACPC,QAAQ;IACRC,SAAS;IACTC,UAAU;IACVC,cAAc;IACdC,SAAS;IACTC,mBAAmB;AACrB,EAAC;AAED,mFAAmF,GACnF,OAAO,MAAMC,kBAA4C;IAAC;IAAU;IAAS;CAAO,CAAA;AACpF,OAAO,MAAMC,kBAA2C;IACtD;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AACD,OAAO,MAAMC,gBAAuC;IAAC;IAAe;CAAU,CAAA;AAC9E,OAAO,MAAMC,iBAAyC;IACpD;IACA;IACA;IACA;IACA;CACD,CAAA;AACD,OAAO,MAAMC,qBAAiD;IAAC;IAAU;CAAQ,CAAA;AAEjF,6BAA6B,GAC7B,OAAO,MAAMC,kBAAkB,GAAE;AAEjC,SAASC,MAAMC,OAAgB,EAAEC,IAAY;IAC3C,OAAOD,WAAW,OAAOA,YAAY,WACjC,AAACA,OAAmC,CAACC,KAAK,GAC1CC;AACN;AAEA,SAASC,OAAOC,KAAc;IAC5B,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ,OAAO,EAAE;IACpC,OAAO;WACF,IAAIG,IACLH,MAAMI,MAAM,CAAC,CAACC,QAA2B,OAAOA,UAAU,YAAYA,UAAU;KAEnF;AACH;AAEA,+DAA+D,GAC/D,OAAO,SAASC,eAAeN,KAAc;IAC3C,OAAO,OAAOA,UAAU,YAAY,IAAIO,OAAO7B,uBAAuB8B,IAAI,CAACR;AAC7E;AAEA,+EAA+E;AAE/E;;;;;;;CAOC,GACD,OAAO,SAASS,eAAeb,OAAgB;IAC7C,OAAOG,OAAOJ,MAAMC,SAAShC;AAC/B;AAEA,yFAAyF,GACzF,OAAO,SAAS8C,oBAAoBC,MAAyB;IAC3D,OAAOZ,OAAOY,QAAQC,KAAK,CAAC,GAAG/C;AACjC;AAEA,8FAA8F,GAC9F,OAAO,SAASgD,kBAAkBF,MAAyB;IACzD,OAAOZ,OAAOY,QAAQG,MAAM,GAAGjD;AACjC;AAUA;;;;CAIC,GACD,OAAO,SAASkD,OAAOC,MAAyB,EAAEC,KAAa;IAC7D,MAAMC,UAAUR,oBAAoBM;IACpC,IAAI,CAACC,OAAO,OAAO;QAAED,QAAQE;QAASC,SAAS;IAAgB;IAC/D,IAAID,QAAQE,QAAQ,CAACH,QAAQ,OAAO;QAAED,QAAQE;QAASC,SAAS;IAAgB;IAChF,IAAID,QAAQJ,MAAM,IAAIjD,iBAAiB,OAAO;QAAEmD,QAAQE;QAASC,SAAS;IAAO;IACjF,OAAO;QAAEH,QAAQ;eAAIE;YAASD;SAAM;QAAEE,SAAS;IAAS;AAC1D;AAEA,oCAAoC,GACpC,OAAO,SAASE,SAASL,MAAyB,EAAEC,KAAa;IAC/D,OAAOlB,OAAOiB,QAAQZ,MAAM,CAAC,CAACkB,KAAOA,OAAOL;AAC9C;AAEA;;;;CAIC,GACD,OAAO,SAASM,iBACdP,MAAyB,EACzBQ,SAAiB,EACjBP,KAAa;IAEb,MAAMC,UAAUR,oBAAoBM;IACpC,IAAI,CAACC,SAASC,QAAQE,QAAQ,CAACH,UAAU,CAACC,QAAQE,QAAQ,CAACI,YAAY,OAAON;IAC9E,OAAOA,QAAQO,GAAG,CAAC,CAACH,KAAQA,OAAOE,YAAYP,QAAQK;AACzD;AAEA;;;CAGC,GACD,OAAO,SAASI,eAAef,MAAyB,EAAEgB,IAAuB;IAC/E,MAAMC,OAAO,IAAIzB,IAAIwB;IACrB,OAAO5B,OAAOY,QACXP,MAAM,CAAC,CAACkB,KAAOM,KAAKC,GAAG,CAACP,KACxBV,KAAK,CAAC,GAAG/C;AACd;AAEA,kFAAkF,GAClF,OAAO,SAASiE,cAAcd,MAAyB,EAAEC,KAAa,EAAEc,MAAc;IACpF,MAAMb,UAAUR,oBAAoBM;IACpC,MAAMgB,OAAOd,QAAQe,OAAO,CAAChB;IAC7B,MAAMiB,KAAKF,OAAQD,CAAAA,SAAS,IAAI,CAAC,IAAI,CAAA;IACrC,IAAIC,SAAS,CAAC,KAAKE,KAAK,KAAKA,MAAMhB,QAAQJ,MAAM,EAAE,OAAOI;IAC1D,MAAMiB,OAAO;WAAIjB;KAAQ;IACzBiB,IAAI,CAACH,KAAK,GAAGG,IAAI,CAACD,GAAG;IACrBC,IAAI,CAACD,GAAG,GAAGjB;IACX,OAAOkB;AACT;AAEA,+EAA+E;AAE/E,iGAAiG,GACjG,OAAO,MAAMC,qBAAqB,cAAa;AAE/C,+EAA+E,GAC/E,OAAO,MAAMC,6BAA6B,EAAC;AAE3C;;;;;CAKC,GACD,OAAO,MAAMC,mBAAmB,IAAG;AAYnC,SAASC,eAAevC,KAAc;IACpC,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ,OAAO,EAAE;IACpC,MAAMwC,OAAO,IAAIrC;IACjB,MAAMsC,OAAqB,EAAE;IAC7B,KAAK,MAAMpC,SAASL,MAAO;QACzB,IAAI,CAACK,SAAS,OAAOA,UAAU,UAAU;QACzC,MAAM,EAAEY,KAAK,EAAEyB,MAAM,EAAE,GAAGrC;QAC1B,IAAI,OAAOY,UAAU,YAAY,CAACA,OAAO;QACzC,IAAI,OAAOyB,WAAW,YAAY,CAACA,UAAUF,KAAKX,GAAG,CAACa,SAAS;QAC/DF,KAAKG,GAAG,CAACD;QACTD,KAAKG,IAAI,CAAC;YAAE3B;YAAOyB;QAAO;IAC5B;IACA,OAAOD;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASI,gBAAgBjD,OAAgB;IAC9C,OAAO2C,eAAe5C,MAAMC,SAASwC;AACvC;AAEA,wEAAwE,GACxE,OAAO,SAASU,qBAAqBnC,MAA6B,EAAEM,KAAa;IAC/E,OAAOsB,eAAe5B,QACnBP,MAAM,CAAC,CAAC2C,MAAQA,IAAI9B,KAAK,KAAKA,OAC9BQ,GAAG,CAAC,CAACsB,MAAQA,IAAIL,MAAM,EACvB9B,KAAK,CAAC,GAAGyB;AACd;AAQA;;;;;;CAMC,GACD,OAAO,SAASW,QAAQrC,MAA6B,EAAEoC,GAAe;IACpE,MAAM7B,UAAUqB,eAAe5B;IAC/B,IAAI,CAACoC,IAAI9B,KAAK,IAAI,CAAC8B,IAAIL,MAAM,EAAE,OAAO;QAAEO,aAAa/B;QAASC,SAAS;IAAgB;IACvF,MAAM+B,cAAcJ,qBAAqB5B,SAAS6B,IAAI9B,KAAK;IAC3D,IAAIiC,YAAY9B,QAAQ,CAAC2B,IAAIL,MAAM,GAAG,OAAO;QAAEO,aAAa/B;QAASC,SAAS;IAAgB;IAC9F,IAAI+B,YAAYpC,MAAM,IAAIuB,4BAA4B;QACpD,OAAO;YAAEY,aAAa/B;YAASC,SAAS;QAAO;IACjD;IACA,6EAA6E;IAC7E,MAAMgB,OAAO;WACRjB,QAAQd,MAAM,CAAC,CAACC,QAAUA,MAAMqC,MAAM,KAAKK,IAAIL,MAAM;QACxD;YAAEzB,OAAO8B,IAAI9B,KAAK;YAAEyB,QAAQK,IAAIL,MAAM;QAAC;KACxC;IACD,MAAOP,KAAKrB,MAAM,GAAGwB,iBAAkB;QACrC,MAAMa,SAAShB,KAAKiB,SAAS,CAAC,CAAC/C,QAAUA,MAAMY,KAAK,KAAK8B,IAAI9B,KAAK;QAClE,IAAIkC,WAAW,CAAC,GAAG;QACnBhB,KAAKkB,MAAM,CAACF,QAAQ;IACtB;IACA,OAAO;QAAEF,aAAad;QAAMhB,SAAS;IAAS;AAChD;AAEA,sCAAsC,GACtC,OAAO,SAASmC,UAAU3C,MAA6B,EAAE+B,MAAc;IACrE,OAAOH,eAAe5B,QAAQP,MAAM,CAAC,CAACC,QAAUA,MAAMqC,MAAM,KAAKA;AACnE;AAEA;;;;CAIC,GACD,OAAO,SAASa,kBACd5C,MAA6B,EAC7Ba,SAAiB,EACjBuB,GAAe;IAEf,MAAM7B,UAAUqB,eAAe5B;IAC/B,MAAMuC,cAAcJ,qBAAqB5B,SAAS6B,IAAI9B,KAAK;IAC3D,IAAI,CAAC8B,IAAIL,MAAM,IAAIQ,YAAY9B,QAAQ,CAAC2B,IAAIL,MAAM,KAAK,CAACQ,YAAY9B,QAAQ,CAACI,YAAY;QACvF,OAAON;IACT;IACA,OAAOA,QACJd,MAAM,CAAC,CAACC,QAAUA,MAAMqC,MAAM,KAAKK,IAAIL,MAAM,EAC7CjB,GAAG,CAAC,CAACpB,QACJA,MAAMqC,MAAM,KAAKlB,YAAY;YAAEP,OAAO8B,IAAI9B,KAAK;YAAEyB,QAAQK,IAAIL,MAAM;QAAC,IAAIrC;AAE9E;AAEA,+EAA+E;AAE/E,4DAA4D,GAC5D,OAAO,SAASmD,eAAe5D,OAAgB;IAC7C,OAAOG,OAAOJ,MAAMC,SAAS9B,oBAAoB8C,KAAK,CAAC,GAAG7C;AAC5D;AAEA,0FAA0F,GAC1F,OAAO,SAAS0F,gBAAgBC,MAAyB,EAAEzC,KAAa;IACtE,IAAI,CAACA,OAAO,OAAOlB,OAAO2D,QAAQ9C,KAAK,CAAC,GAAG7C;IAC3C,OAAO;QAACkD;WAAUlB,OAAO2D,QAAQtD,MAAM,CAAC,CAACkB,KAAOA,OAAOL;KAAO,CAACL,KAAK,CAAC,GAAG7C;AAC1E;AAgBA;;;;;;CAMC,GACD,OAAO,SAAS4F,eACdC,MAAyB,EACzB5C,MAAyB,EACzB0C,MAAyB;IAEzB,MAAMG,UAAU9D,OAAO6D;IACvB,MAAME,UAAU,IAAI3D,IAAI0D;IACxB,MAAMpB,OAAO/B,oBAAoBM,QAAQZ,MAAM,CAAC,CAACkB,KAAOwC,QAAQjC,GAAG,CAACP;IACpE,MAAMyC,YAAY,IAAI5D,IAAIsC;IAC1B,IAAIoB,QAAQ/C,MAAM,IAAI7C,6BAA6B;QACjD,OAAO;YACL+C,QAAQyB;YACRiB,QAAQ,EAAE;YACVM,MAAMH,QAAQzD,MAAM,CAAC,CAACkB,KAAO,CAACyC,UAAUlC,GAAG,CAACP;YAC5C2C,SAAS;YACTC,OAAOL,QAAQ/C,MAAM;QACvB;IACF;IACA,OAAO;QACLE,QAAQyB;QACRiB,QAAQ3D,OAAO2D,QACZtD,MAAM,CAAC,CAACkB,KAAOwC,QAAQjC,GAAG,CAACP,OAAO,CAACyC,UAAUlC,GAAG,CAACP,KACjDV,KAAK,CAAC,GAAG5C;QACZgG,MAAM,EAAE;QACRC,SAAS;QACTC,OAAOL,QAAQ/C,MAAM;IACvB;AACF;AAEA,+EAA+E;AAE/E,sEAAsE,GACtE,OAAO,SAASqD,eACdC,IAA8D;QAEzBA;IAArC,OAAO,GAAGA,KAAKC,IAAI,CAAC,CAAC,EAAED,KAAKnD,KAAK,CAAC,CAAC,GAAEmD,eAAAA,KAAK1B,MAAM,YAAX0B,eAAe,GAAG,CAAC,EAAEA,KAAK9C,EAAE,EAAE;AACrE;AAEA,SAASgD,UAAUtE,KAAc;IAC/B,IAAI,CAACA,SAAS,OAAOA,UAAU,YAAYC,MAAMC,OAAO,CAACF,QAAQ,OAAOF;IACxE,MAAMyE,MAA8B,CAAC;IACrC,KAAK,MAAM,CAACC,KAAKnE,MAAM,IAAIoE,OAAOC,OAAO,CAAC1E,OAAmC;QAC3E,IAAI,OAAOK,UAAU,UAAUkE,GAAG,CAACC,IAAI,GAAGnE;IAC5C;IACA,OAAOoE,OAAOE,IAAI,CAACJ,KAAKzD,MAAM,GAAGyD,MAAMzE;AACzC;AAEA,SAAS8E,QAAQ5E,KAAc;IAC7B,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU,OAAO;IAChD,MAAM6E,MAAM7E;IACZ,MAAM8E,OAAO,CAACN,MAAiB,OAAOK,GAAG,CAACL,IAAI,KAAK,WAAYK,GAAG,CAACL,IAAI,GAAc;IACrF,MAAMH,OAAOS,KAAK;IAClB,MAAMxD,KAAKwD,KAAK;IAChB,MAAM7D,QAAQ6D,KAAK;IACnB,MAAMC,SAASD,KAAK;IACpB,IAAI,CAACT,QAAQ,CAAC/C,MAAM,CAACL,SAAS,CAAC8D,QAAQ,OAAO;IAC9C,MAAMrC,SAASoC,KAAK;IACpB,MAAME,SAASV,UAAUO,GAAG,CAAC,SAAS;IACtC,OAAO;QACLR;QACA/C;QACAL;OACIyB,SAAS;QAAEA;IAAO,IAAI,CAAC;QAC3BuC,OAAOH,KAAK,SAASlE,KAAK,CAAC,GAAGnC;QAC9BsG;OACIC,SAAS;QAAEA;IAAO,IAAI,CAAC;AAE/B;AAEA,SAASE,GAAGlF,KAAc;IACxB,OAAO,OAAOA,UAAU,YAAYmF,OAAOC,QAAQ,CAACpF,SAASA,QAAQ;AACvE;AAEA,SAASqF,YAAYrF,KAAc;IACjC,OAAO,AAACrB,YAAmCyC,QAAQ,CAACpB;AACtD;AAEA,6FAA6F,GAC7F,OAAO,SAASsF,YAAY1F,OAAgB;IAC1C,MAAMe,SAAShB,MAAMC,SAAS1B;IAC9B,IAAI,CAAC+B,MAAMC,OAAO,CAACS,SAAS,OAAO,EAAE;IACrC,MAAM6B,OAAO,IAAIrC;IACjB,MAAMoE,MAAqB,EAAE;IAC7B,KAAK,MAAMlE,SAASM,OAAQ;QAC1B,MAAM4E,MAAMX,QAAQvE;QACpB,IAAI,CAACkF,KAAK;QACV,MAAMf,MAAML,eAAeoB;QAC3B,IAAI/C,KAAKX,GAAG,CAAC2C,MAAM;QACnBhC,KAAKG,GAAG,CAAC6B;QACT,MAAMgB,QAAQ,AAACnF,KAAiC,CAAC,QAAQ;QACzDkE,IAAI3B,IAAI,CAAC,aACJ2C,KACCF,YAAYG,SAAS;YAAEA;QAAM,IAAI,CAAC;YACtCC,aAAaP,GAAG,AAAC7E,KAAiC,CAAC,cAAc;;IAErE;IACA,OAAOkE,IAAI3D,KAAK,CAAC,GAAGzC;AACtB;AAUA,sFAAsF,GACtF,OAAO,SAASuH,cACdC,OAA+B,EAC/BvB,IAAoB,EACpBwB,KAAa;IAEb,MAAM1E,UAAUoE,YAAY;QAAE,CAACpH,cAAc,EAAEyH;IAAQ;IACvD,MAAMJ,MAAMX,QAAQR;IACpB,IAAI,CAACmB,KAAK,OAAO;QAAEI,SAASzE;QAASC,SAAS;IAAO;IACrD,MAAMqD,MAAML,eAAeoB;IAC3B,IAAIrE,QAAQ2E,IAAI,CAAC,CAACxF,QAAU8D,eAAe9D,WAAWmE,MAAM;QAC1D,OAAO;YACLmB,SAASzE,QAAQd,MAAM,CAAC,CAACC,QAAU8D,eAAe9D,WAAWmE;YAC7DrD,SAAS;QACX;IACF;IACA,IAAID,QAAQJ,MAAM,IAAI3C,aAAa,OAAO;QAAEwH,SAASzE;QAASC,SAAS;IAAO;IAC9E,OAAO;QAAEwE,SAAS;eAAIzE;YAAS,aAAKqE;gBAAKE,aAAaG;;SAAQ;QAAEzE,SAAS;IAAU;AACrF;AAEA,6EAA6E,GAC7E,OAAO,SAAS2E,aACdH,OAA+B,EAC/BnB,GAAW,EACXgB,KAAuB;IAEvB,OAAOF,YAAY;QAAE,CAACpH,cAAc,EAAEyH;IAAQ,GAAGlE,GAAG,CAAC,CAACpB;QACpD,IAAI8D,eAAe9D,WAAWmE,KAAK,OAAOnE;QAC1C,MAAM8B,OAAoB,aAAK9B;QAC/B,OAAO8B,KAAKqD,KAAK;QACjB,OAAOA,SAASH,YAAYG,SAAS,aAAKrD;YAAMqD;aAAUrD;IAC5D;AACF;AAEA,yFAAyF,GACzF,OAAO,SAAS4D,YAAYnG,OAAgB;IAC1C,MAAMe,SAAShB,MAAMC,SAASxB;IAC9B,IAAI,CAAC6B,MAAMC,OAAO,CAACS,SAAS,OAAO,EAAE;IACrC,MAAM6B,OAAO,IAAIrC;IACjB,MAAMoE,MAAoB,EAAE;IAC5B,KAAK,MAAMlE,SAASM,OAAQ;QAC1B,MAAM4E,MAAMX,QAAQvE;QACpB,IAAI,CAACkF,KAAK;QACV,MAAMf,MAAML,eAAeoB;QAC3B,IAAI/C,KAAKX,GAAG,CAAC2C,MAAM;QACnBhC,KAAKG,GAAG,CAAC6B;QACTD,IAAI3B,IAAI,CAAC,aAAK2C;YAAKS,YAAYd,GAAG,AAAC7E,KAAiC,CAAC,aAAa;;IACpF;IACA,OAAOkE,IAAI3D,KAAK,CAAC,GAAGvC;AACtB;AAEA,qFAAqF,GACrF,OAAO,SAAS4H,aACdC,OAA8B,EAC9B9B,IAAoB,EACpBwB,KAAa;IAEb,MAAM1E,UAAU6E,YAAY;QAAE,CAAC3H,cAAc,EAAE8H;IAAQ;IACvD,MAAMX,MAAMX,QAAQR;IACpB,IAAI,CAACmB,KAAK,OAAOrE;IACjB,MAAMsD,MAAML,eAAeoB;IAC3B,OAAO;QACL,aAAKA;YAAKS,YAAYJ;;WACnB1E,QAAQd,MAAM,CAAC,CAACC,QAAU8D,eAAe9D,WAAWmE;KACxD,CAAC5D,KAAK,CAAC,GAAGvC;AACb;AAEA,+EAA+E;AAE/E,wEAAwE,GACxE,OAAO,SAAS8H,oBAAoBvG,OAAgB;IAClD,MAAMe,SAAShB,MAAMC,SAAStB;IAC9B,IAAI,CAACqC,UAAU,OAAOA,WAAW,YAAYV,MAAMC,OAAO,CAACS,SAAS,OAAO,CAAC;IAC5E,MAAM4D,MAA8B,CAAC;IACrC,KAAK,MAAM,CAACtD,OAAOuE,MAAM,IAAIf,OAAOC,OAAO,CAAC/D,QAAoC;QAC9E,IAAIM,SAASX,eAAekF,QAAQjB,GAAG,CAACtD,MAAM,GAAGuE;QACjD,IAAIf,OAAOE,IAAI,CAACJ,KAAKzD,MAAM,IAAIvC,sBAAsB;IACvD;IACA,OAAOgG;AACT;AAYA;;;;;;;CAOC,GACD,OAAO,SAAS6B,sBACdC,MAAe,EACfC,UAAmB,EACnBC,SAAkB,EAClBnH,iBAA0B;IAE1B,IAAIkB,eAAe+F,SAAS,OAAO;QAAEb,OAAOa;QAAQG,QAAQ;QAAUC,UAAU;IAAK;IACrF,IAAInG,eAAegG,aAAa;QAC9B,OAAO;YAAEd,OAAOc;YAAYE,QAAQ;YAASC,UAAUrH;QAAkB;IAC3E;IACA,IAAIkB,eAAeiG,YAAY;QAC7B,OAAO;YAAEf,OAAOe;YAAWC,QAAQ;YAAQC,UAAUrH;QAAkB;IACzE;IACA,OAAO;QAAEoG,OAAO;QAAMgB,QAAQ;QAAQC,UAAU;IAAM;AACxD;AAEA,+EAA+E;AAE/E,SAASC,MAAwB1G,KAAc,EAAE2G,OAAqB,EAAEC,QAAW;IACjF,OAAO,AAACD,QAA+BvF,QAAQ,CAACpB,SAAUA,QAAc4G;AAC1E;AAEA,2FAA2F,GAC3F,OAAO,SAASC,aAAajH,OAAgB;IAC3C,MAAMe,SAAShB,MAAMC,SAASpB;IAC9B,MAAMqG,MACJlE,UAAU,OAAOA,WAAW,YAAY,CAACV,MAAMC,OAAO,CAACS,UAClDA,SACD,CAAC;IACP,MAAM5B,SAAS8F,GAAG,CAAC,SAAS;IAC5B,MAAM1F,UAAU0F,GAAG,CAAC,UAAU;IAC9B,OAAO;QACLhG,YAAY6H,MAAM7B,GAAG,CAAC,aAAa,EAAExF,iBAAiBT,kBAAkBC,UAAU;QAClFC,OAAO4H,MAAM7B,GAAG,CAAC,QAAQ,EAAEvF,iBAAiBV,kBAAkBE,KAAK;QACnEC,QAAQA,WAAW,WAAWuB,eAAevB,UAAUA,SAASH,kBAAkBG,MAAM;QACxFC,SAAS0H,MAAM7B,GAAG,CAAC,UAAU,EAAEtF,eAAeX,kBAAkBI,OAAO;QACvEC,UAAUyH,MAAM7B,GAAG,CAAC,WAAW,EAAErF,gBAAgBZ,kBAAkBK,QAAQ;QAC3EC,cAAcwH,MAAM7B,GAAG,CAAC,eAAe,EAAEpF,oBAAoBb,kBAAkBM,YAAY;QAC3FC,SACE,OAAOA,YAAY,YAAYA,YAAY,MAAMA,QAAQ2B,MAAM,IAAIpB,kBAC/DP,UACAP,kBAAkBO,OAAO;QAC/BC,mBACE,OAAOyF,GAAG,CAAC,oBAAoB,KAAK,YAC/BA,GAAG,CAAC,oBAAoB,GACzBjG,kBAAkBQ,iBAAiB;IAC3C;AACF"}
|
|
@@ -113,10 +113,11 @@ export interface AnalyticsEventParams {
|
|
|
113
113
|
};
|
|
114
114
|
/**
|
|
115
115
|
* Custom: no GA4 equivalent (AGL-3594). How a person chose to start a new
|
|
116
|
-
* site in the guided start: the ready-made starter, or
|
|
116
|
+
* site in the guided start: the ready-made starter, AI, or bringing in the
|
|
117
|
+
* site they have now (AGL-3976).
|
|
117
118
|
*/
|
|
118
119
|
site_start_choice: {
|
|
119
|
-
choice: 'starter' | 'ai';
|
|
120
|
+
choice: 'starter' | 'ai' | 'import';
|
|
120
121
|
};
|
|
121
122
|
/**
|
|
122
123
|
* Custom: no GA4 equivalent (AGL-3898). The guided start's steps, as a
|