@aglyn/aglyn 1.0.0-beta.270 → 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.
Files changed (90) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-preferences.d.ts +50 -0
  3. package/src/lib/app-utils/account-preferences.js +96 -0
  4. package/src/lib/app-utils/account-preferences.js.map +1 -1
  5. package/src/lib/app-utils/activity-labels.js +2 -0
  6. package/src/lib/app-utils/activity-labels.js.map +1 -1
  7. package/src/lib/app-utils/analytics-events.d.ts +16 -2
  8. package/src/lib/app-utils/analytics-events.js +2 -0
  9. package/src/lib/app-utils/analytics-events.js.map +1 -1
  10. package/src/lib/app-utils/append-only-history.d.ts +94 -0
  11. package/src/lib/app-utils/append-only-history.js +73 -0
  12. package/src/lib/app-utils/append-only-history.js.map +1 -0
  13. package/src/lib/app-utils/bundle-intent.d.ts +186 -0
  14. package/src/lib/app-utils/bundle-intent.js +386 -0
  15. package/src/lib/app-utils/bundle-intent.js.map +1 -0
  16. package/src/lib/app-utils/client-build.d.ts +207 -0
  17. package/src/lib/app-utils/client-build.js +237 -0
  18. package/src/lib/app-utils/client-build.js.map +1 -0
  19. package/src/lib/app-utils/console-routes.d.ts +4 -0
  20. package/src/lib/app-utils/console-routes.js +2 -0
  21. package/src/lib/app-utils/console-routes.js.map +1 -1
  22. package/src/lib/app-utils/docs-help-sections.generated.js +13 -6
  23. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  24. package/src/lib/app-utils/docs-help.generated.d.ts +11 -5
  25. package/src/lib/app-utils/docs-help.generated.js +31 -2
  26. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  27. package/src/lib/app-utils/docs-index.generated.js +105 -35
  28. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  29. package/src/lib/app-utils/margin-utilization.js +6 -4
  30. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  31. package/src/lib/app-utils/media-filter.d.ts +26 -0
  32. package/src/lib/app-utils/media-filter.js +32 -0
  33. package/src/lib/app-utils/media-filter.js.map +1 -1
  34. package/src/lib/app-utils/onboarding-deep-link.d.ts +36 -2
  35. package/src/lib/app-utils/onboarding-deep-link.js +48 -5
  36. package/src/lib/app-utils/onboarding-deep-link.js.map +1 -1
  37. package/src/lib/app-utils/operator-alerts.js +21 -0
  38. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  39. package/src/lib/app-utils/owner-approval-record.d.ts +102 -0
  40. package/src/lib/app-utils/owner-approval-record.js +148 -0
  41. package/src/lib/app-utils/owner-approval-record.js.map +1 -0
  42. package/src/lib/app-utils/plan-card-meters.d.ts +96 -0
  43. package/src/lib/app-utils/plan-card-meters.js +197 -0
  44. package/src/lib/app-utils/plan-card-meters.js.map +1 -0
  45. package/src/lib/app-utils/plan-entitlements.d.ts +183 -67
  46. package/src/lib/app-utils/plan-entitlements.js +213 -58
  47. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  48. package/src/lib/app-utils/release-flags.d.ts +1 -1
  49. package/src/lib/app-utils/release-flags.js +22 -0
  50. package/src/lib/app-utils/release-flags.js.map +1 -1
  51. package/src/lib/app-utils/server.d.ts +4 -0
  52. package/src/lib/app-utils/server.js +7 -0
  53. package/src/lib/app-utils/server.js.map +1 -1
  54. package/src/lib/app-utils/site-care-project.d.ts +227 -0
  55. package/src/lib/app-utils/site-care-project.js +423 -0
  56. package/src/lib/app-utils/site-care-project.js.map +1 -0
  57. package/src/lib/app-utils/site-care.d.ts +291 -0
  58. package/src/lib/app-utils/site-care.js +310 -0
  59. package/src/lib/app-utils/site-care.js.map +1 -0
  60. package/src/lib/app-utils/upgrade-proposal.d.ts +29 -4
  61. package/src/lib/app-utils/upgrade-proposal.js +60 -9
  62. package/src/lib/app-utils/upgrade-proposal.js.map +1 -1
  63. package/src/lib/foundation/definitions/org-billing.types.d.ts +40 -10
  64. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  65. package/src/lib/foundation/definitions/organization.types.d.ts +3 -1
  66. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  67. package/src/lib/foundation/definitions/platform.types.d.ts +20 -0
  68. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  69. package/src/lib/foundation/index.d.ts +1 -1
  70. package/src/lib/foundation/index.js.map +1 -1
  71. package/src/lib/plugin-manager/feature-plugins.d.ts +48 -0
  72. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  73. package/src/lib/plugin-manager/first-party-plugins.generated.js +16 -0
  74. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  75. package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +19 -0
  76. package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -1
  77. package/src/lib/plugin-manager/plugin-domain-search.d.ts +81 -0
  78. package/src/lib/plugin-manager/plugin-domain-search.js +63 -0
  79. package/src/lib/plugin-manager/plugin-domain-search.js.map +1 -0
  80. package/src/lib/plugin-manager/plugin-entitlements.d.ts +23 -5
  81. package/src/lib/plugin-manager/plugin-entitlements.js +10 -0
  82. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  83. package/src/lib/plugin-manager/plugin-media-ingest.d.ts +18 -1
  84. package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
  85. package/src/lib/plugin-manager/plugin-site-resources.d.ts +74 -0
  86. package/src/lib/plugin-manager/plugin-site-resources.js +135 -0
  87. package/src/lib/plugin-manager/plugin-site-resources.js.map +1 -0
  88. package/src/lib/plugin-manager/purchase-gate-preclearance.d.ts +61 -0
  89. package/src/lib/plugin-manager/purchase-gate-preclearance.js +102 -0
  90. package/src/lib/plugin-manager/purchase-gate-preclearance.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.270",
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.270",
41
- "@aglyn/shared-data-mdi": "1.0.0-beta.270",
42
- "@aglyn/shared-data-types": "1.0.0-beta.270",
43
- "@aglyn/shared-util-email": "1.0.0-beta.270",
44
- "@aglyn/shared-util-first-touch": "1.0.0-beta.270",
45
- "@aglyn/shared-util-http": "1.0.0-beta.270",
46
- "@aglyn/shared-util-logger": "1.0.0-beta.270",
47
- "@aglyn/shared-util-timestamp": "1.0.0-beta.270",
48
- "@aglyn/shared-util-tools": "1.0.0-beta.270",
49
- "@aglyn/shared-util-vendor": "1.0.0-beta.270",
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":"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"}
@@ -77,6 +77,7 @@ import { isPluginJobTargetType, pluginActivityActionSentence, pluginActivityTarg
77
77
  'billing.assistOverage.setCap': 'Set the AI overage ceiling',
78
78
  'billing.assistOverage.setHardCap': 'Set the AI stop-at-band switch',
79
79
  'billing.autoLocked': 'Locked automatically for billing',
80
+ 'billing.bundle-link-minted': 'Made a bundle checkout link',
80
81
  'billing.collaboratorAllocation': 'Changed collaborator seats',
81
82
  'billing.dispute.opened': 'Payment dispute opened',
82
83
  'billing.disputeClosed': 'Payment dispute closed',
@@ -101,6 +102,7 @@ import { isPluginJobTargetType, pluginActivityActionSentence, pluginActivityTarg
101
102
  'billing.webhookInert': 'Ignored a billing webhook',
102
103
  'billing.webhookSignatureRejected': 'Rejected a billing webhook signature',
103
104
  'broadcast.send': 'Sent a broadcast',
105
+ 'client-build.preview-sent': 'Sent a client build preview',
104
106
  'contact.phone.erased': 'Erased a phone number',
105
107
  'contact.suppression.recorded': 'Added a contact suppression',
106
108
  'contact.suppression.released': 'Released a contact suppression',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-labels.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 * ONE LABEL MAP FOR EVERY ACTIVITY AND AUDIT LIST (AGL-3660).\n *\n * Two logs feed the console's lists. The activity logs\n * (`orgs/{id}/activity`, `hosts/{id}/activity`) store a prose sentence or a\n * plugin's code plus a `{ type, id, name }` target. The staff audit trail\n * (`adminAudit`) stores a dotted code (`ai.job.output`, `org.override`) and\n * a Firestore PATH as its target (`orgs/abc/aiJobs/xyz`). The staff tables\n * printed both raw, so a reader saw `ai.job.output` next to\n * `orgs/9fK…/aiJobs/Qe2…` and had to know the schema to read their own\n * audit trail.\n *\n * Every list now draws its words from here: an action as a sentence\n * (`Created page Home with Aglyn AI`), a target as the site and the item,\n * and — for an audit row — why, the credits it spent and what came of it.\n * The raw code and path are kept on the description, for a tooltip and for\n * the staff details dialog, and never as the visible text.\n */\n\nimport {\n activityActionLabel,\n activityPrimaryText,\n activityTypeLabel,\n type ActivityEntryLike,\n type ActivityTargetLike,\n} from './activity-presenter'\nimport { orgOverrideReasonSummary } from './org-override-reason'\nimport {\n isPluginJobTargetType,\n pluginActivityActionSentence,\n pluginActivityTargetLabel,\n pluginStaffAuditActionLabel,\n pluginStaffAuditCollection,\n pluginStaffAuditDescribe,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** What a list row and the details dialog show for one entry. */\nexport interface ActivityDescription {\n /** The act, as a sentence. Never a code. */\n action: string\n /** What it was done to, in words: the site and the item. Never a path. */\n target: string\n /** Why it was done, when the writer recorded a reason. */\n why: string | null\n /** AI credits it spent, when it spent any. */\n credits: number | null\n /** What came of it, when the writer recorded an outcome. */\n result: string | null\n /** The stored code, when the action was one — for a tooltip and staff. */\n code: string | null\n /** The stored target path (audit) or `type/id` (activity) — staff only. */\n path: string | null\n /** The organization the entry concerns, when it names one. */\n orgId: string | null\n /** The site the entry concerns, when it names one. */\n hostId: string | null\n /** The plugin job behind the entry (an AI job), when there is one. */\n jobId: string | null\n /** The account the entry concerns, when it names one. */\n uid: string | null\n}\n\n/** Names a route resolved for the ids an entry carries. */\nexport interface ActivityNames {\n /** Organization name by org id. */\n orgs?: Readonly<Record<string, string | null | undefined>>\n /** Site name by host id. */\n hosts?: Readonly<Record<string, string | null | undefined>>\n /** An account's address (or name) by uid. */\n users?: Readonly<Record<string, string | null | undefined>>\n}\n\n/** `screen` → `page`, `Home` → `page Home`: the noun a sentence uses. */\nexport function activityTargetPhrase(target: ActivityTargetLike | null | undefined): string {\n const noun = activityTypeLabel(target?.type)\n const lower = noun === 'Item' ? 'item' : noun.charAt(0).toLowerCase() + noun.slice(1)\n const name = target?.name?.trim()\n return name ? `${lower} ${name}` : lower\n}\n\n/**\n * An activity row's action as a sentence about its target. A plugin code\n * with a declared sentence fills it (`Created {target} with Aglyn AI`); any\n * other code reads as its label, and a prose action as itself.\n */\nexport function activityActionSentence(\n action: string | undefined,\n target?: ActivityTargetLike | null,\n): string {\n const stored = action?.trim() ?? ''\n const sentence = pluginActivityActionSentence(stored)\n if (sentence) return sentence.replace('{target}', activityTargetPhrase(target))\n return activityActionLabel(stored)\n}\n\n/**\n * A one-column feed's line: the sentence when the code declares one, else\n * the action with its target's name (`Saved the screen — Home`).\n */\nexport function activityRowText(entry: ActivityEntryLike): string {\n const stored = entry.action?.trim() ?? ''\n return pluginActivityActionSentence(stored)\n ? activityActionSentence(stored, entry.target)\n : activityPrimaryText(entry)\n}\n\n/** Whether a stored action is a dotted code rather than a sentence. */\nexport function isActivityCode(action: unknown): action is string {\n return typeof action === 'string' && /^[a-z][\\w-]*(\\.[\\w-]+)+$/i.test(action.trim())\n}\n\n/*==========================================\n * THE STAFF AUDIT TRAIL'S WORDS.\n *\n * Every code core's doors write. A plugin's doors declare their own through\n * `staffAuditLabels` (the AI plugin's `ai.job.*`), and a code neither knows\n * is humanized from its segments rather than shown raw, so a new writer\n * reads as `Billing: invoice paid` until somebody names it properly.\n *==========================================*/\nexport const STAFF_AUDIT_ACTION_LABELS: Readonly<Record<string, string>> = {\n 'account.closed.self': 'Closed their own account',\n 'account.exported.self': 'Exported their own account data',\n 'ai-usage.exported': 'Exported AI usage',\n 'billing.assistOverage.setCap': 'Set the AI overage ceiling',\n 'billing.assistOverage.setHardCap': 'Set the AI stop-at-band switch',\n 'billing.autoLocked': 'Locked automatically for billing',\n 'billing.collaboratorAllocation': 'Changed collaborator seats',\n 'billing.dispute.opened': 'Payment dispute opened',\n 'billing.disputeClosed': 'Payment dispute closed',\n 'billing.disputeOpened': 'Payment dispute opened',\n 'billing.disputeUnattributed': 'Unattributed payment dispute',\n 'billing.invoice': 'Invoice event',\n 'billing.invoice.closed': 'Invoice closed',\n 'billing.invoice.failed': 'Invoice payment failed',\n 'billing.invoice.paid': 'Invoice paid',\n 'billing.manage': 'Opened billing management',\n 'billing.orphanedSubscription': 'Found a subscription with no workspace',\n 'billing.paymentFailed': 'Payment failed',\n 'billing.paymentMethod.changed': 'Changed the payment method',\n 'billing.platformDisputeLost': 'Lost a payment dispute',\n 'billing.platformInvoiceUncollectible': 'Invoice marked uncollectible',\n 'billing.registerAllocation': 'Changed register allocation',\n 'billing.storageOverage.setCap': 'Set the storage overage ceiling',\n 'billing.subscriptionCanceled': 'Subscription canceled',\n 'billing.usage': 'Recorded usage',\n 'billing.usageBudget.set': 'Set the usage budget',\n 'billing.view': 'Viewed billing',\n 'billing.webhookInert': 'Ignored a billing webhook',\n 'billing.webhookSignatureRejected': 'Rejected a billing webhook signature',\n 'broadcast.send': 'Sent a broadcast',\n 'contact.phone.erased': 'Erased a phone number',\n 'contact.suppression.recorded': 'Added a contact suppression',\n 'contact.suppression.released': 'Released a contact suppression',\n 'coupon.create': 'Created a coupon',\n 'coupon.promotion_code.update': 'Updated a promotion code',\n 'data.transfer.export': 'Exported data',\n 'data.transfer.plan': 'Planned a data transfer',\n 'email.history-imported': 'Imported email history',\n 'email.message-viewed': 'Viewed an email message',\n 'email.sending-domains.reap': 'Removed unused sending domains',\n 'email.suppression.release': 'Released an email suppression',\n 'emailSendRate.update': 'Changed the email send rate',\n 'erasure.runBatch': 'Ran an erasure batch',\n 'erasures.replayed': 'Replayed erasures',\n 'firestore.export': 'Exported the database',\n 'firstPartyHosts.update': 'Changed first-party sites',\n 'flags.update': 'Changed feature flags',\n 'freeWorkspaceCap.update': 'Changed the free workspace cap',\n 'host.deleted': 'Deleted a site',\n 'host.domain.released': 'Released a domain',\n 'host.reattach-domain': 'Reattached a domain',\n 'host.set-subdomain': 'Changed a site address',\n 'host.transfer': 'Transferred a site',\n 'lockdown.lock': 'Locked the workspace down',\n 'lockdown.payouts-pause': 'Paused payouts',\n 'lockdown.payouts-restore': 'Restored payouts',\n 'lockdown.renewals-pause': 'Paused renewals',\n 'lockdown.renewals-resume': 'Resumed renewals',\n 'lockdown.resend-notice': 'Resent the lockdown notice',\n 'maintenance.auditArchive.run': 'Archived the audit log',\n 'maintenance.reapArtifacts.run': 'Removed stale build artifacts',\n 'maintenance.reverifyPlugins.run': 'Re-verified plugins',\n 'operatorAlerts.test': 'Sent a test operator alert',\n 'operatorAlerts.update': 'Changed operator alerts',\n 'org.acquisition-viewed': 'Viewed where the organization came from',\n 'org.auditLog': 'Viewed the organization audit log',\n 'org.discount.apply': 'Applied a discount',\n 'org.discount.remove': 'Removed a discount',\n 'org.enterprise.provision': 'Provisioned enterprise billing',\n 'org.erase-failed': 'Organization erasure failed',\n 'org.erased': 'Erased the organization',\n 'org.erasureCanceled': 'Canceled the organization erasure',\n 'org.erasureRequested': 'Requested organization erasure',\n 'org.exported': 'Exported the organization',\n 'org.name': 'Renamed the organization',\n 'org.note': 'Added a staff note',\n 'org.override': 'Overrode the plan',\n 'org.refund': 'Issued a refund',\n 'org.seatAddons.changed': 'Changed seat add-ons',\n 'org.sso.enforceSignInMethods': 'Changed enforced sign-in methods',\n 'org.sso.jit': 'Changed single sign-on provisioning',\n 'org.subscription-cancel': 'Canceled the subscription',\n 'org.subscriptionStarted.settle': 'Settled a new subscription',\n 'org.successManagerCleared': 'Removed the success manager',\n 'org.successManagerSet': 'Assigned a success manager',\n 'org.upgradeProposal.propose': 'Proposed an upgrade',\n 'org.upgradeProposal.withdraw': 'Withdrew an upgrade proposal',\n 'orgs.unverified.reap': 'Removed unverified organizations',\n 'person.erased': 'Erased a person',\n 'person.erasure-requested': 'Requested erasure of a person',\n 'plan.name': 'Renamed a plan',\n 'plugins.artifacts.reap': 'Removed stale plugin builds',\n 'plugins.remoteServer.load': 'Loaded a remote plugin server',\n 'plugins.verification.decline': 'Declined a plugin verification',\n 'plugins.verifier.regression': 'Plugin verifier regression',\n 'security.pageSecurityHold': 'Put a page on security hold',\n 'security.paymentFraudHold': 'Put a workspace on hold for payment fraud',\n 'security.staffGranted': 'Granted staff access',\n 'site.package.apply': 'Applied a site package',\n 'site.package.undo': 'Undid a site package',\n 'sso.domain.driftDetected': 'Single sign-on domain drift detected',\n 'staff.evidence': 'Recorded evidence',\n 'staff.paymentFailed': 'Payment failed (staff notice)',\n 'staff.planChanged': 'Plan changed (staff notice)',\n 'staff.subscriptionCanceled': 'Subscription canceled (staff notice)',\n 'staff.subscriptionStarted': 'Subscription started (staff notice)',\n 'systemEmail.staffSend': 'Sent a system email',\n 'systemEmail.test': 'Sent a test system email',\n 'taxFilingConfig.clear': 'Cleared the tax filing settings',\n 'taxFilingConfig.update': 'Changed the tax filing settings',\n 'taxablePurchases.clear': 'Cleared taxable purchases',\n 'taxablePurchases.update': 'Changed taxable purchases',\n 'user.acquisition-viewed': 'Viewed where the account came from',\n 'user.erased': 'Erased the account',\n 'user.impersonate': 'Signed in as the account',\n 'user.nativeAppCheck': 'Verified the desktop app for the account',\n 'user.nativeAppSignIn': 'Signed in to the app through the console',\n 'user.sendPasswordReset': 'Sent a password reset',\n 'user.setAdAudienceOptOut': 'Changed the ad audience opt-out',\n 'user.setPassword': 'Set the password',\n 'user.signOutDevice': 'Signed a device out',\n 'user.updateProfile': 'Updated the profile',\n}\n\n/** Leading namespaces as a reader names them. */\nconst NAMESPACE_LABELS: Readonly<Record<string, string>> = {\n org: 'Organization',\n orgs: 'Organizations',\n user: 'Account',\n host: 'Site',\n}\n\n/** `paymentMethod` → `payment method`, `reattach-domain` → `reattach domain`. */\nfunction words(segment: string): string {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n}\n\n/** A code nobody named, as words: `billing.invoice.paid` → `Billing: invoice paid`. */\nexport function humanizeActivityCode(code: string): string {\n const [head = '', ...rest] = code.trim().split('.').filter(Boolean)\n const lead = NAMESPACE_LABELS[head] ?? words(head).replace(/^./, (c) => c.toUpperCase())\n if (!rest.length) return lead\n return `${lead}: ${rest.map(words).join(' ')}`\n}\n\n/** What a staff audit code reads as, everywhere it is shown. */\nexport function staffAuditActionLabel(action: string | null | undefined): string {\n const code = action?.trim() ?? ''\n if (!code) return '—'\n return (\n STAFF_AUDIT_ACTION_LABELS[code] ??\n pluginStaffAuditActionLabel(code) ??\n (isActivityCode(code) ? humanizeActivityCode(code) : code)\n )\n}\n\n/** The collections an audit path walks, named for a reader. */\nconst COLLECTION_NOUNS: Readonly<Record<string, string>> = {\n orgs: 'Organization',\n hosts: 'Site',\n users: 'Account',\n members: 'Member',\n invites: 'Invitation',\n screens: 'Page',\n layouts: 'Layout',\n components: 'Component',\n templates: 'Template',\n media: 'Media',\n emailDeliveries: 'Email',\n coupons: 'Coupon',\n flags: 'Feature flag',\n plugins: 'Plugin',\n subscriptions: 'Subscription',\n}\n\n/** `orgs/a/aiJobs/b` → `{ orgs: 'a', aiJobs: 'b' }`, in walk order. */\nfunction pathPairs(path: string): Array<[string, string]> {\n const segments = path.split('/').filter(Boolean)\n const pairs: Array<[string, string]> = []\n for (let at = 0; at < segments.length; at += 2) {\n pairs.push([segments[at] ?? '', segments[at + 1] ?? ''])\n }\n return pairs\n}\n\n/** The stored audit row, read defensively. */\nexport interface StaffAuditEntryLike {\n action?: string | null\n target?: string | null\n subjectUid?: string | null\n reason?: string | null\n note?: string | null\n /** The state the act left; AI rows carry `{ label, hostId, credits }`. */\n after?: Record<string, unknown> | null\n}\n\nconst str = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\nconst num = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** What an audit act did, in words, and the words it was about. */\nexport function describeStaffAudit(\n entry: StaffAuditEntryLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const code = str(entry.action)\n const path = str(entry.target)\n const after = entry.after ?? {}\n const pairs = path && path.includes('/') ? pathPairs(path) : []\n const idOf = (collection: string) => pairs.find(([name]) => name === collection)?.[1] || null\n const orgId = idOf('orgs')\n // A plugin's job collection (the AI plugin's jobs) names the job the row\n // links to; core knows it only through the plugin's declaration.\n const jobPair = pairs.find(([name]) => pluginStaffAuditCollection(name)?.job)\n const jobId = jobPair?.[1] || null\n const hostId = str(after['hostId']) ?? idOf('hosts')\n const uid = idOf('users') ?? str(entry.subjectUid)\n const label = str(after['label'])\n // A plugin may word the row from what the act left: the AI plugin names\n // what a job made (`Created page Home with Aglyn AI`).\n const declared = code ? pluginStaffAuditDescribe(code, after) : undefined\n const action = declared?.action ?? staffAuditActionLabel(code)\n\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const org = orgId ? (names.orgs?.[orgId] ?? null) : null\n const person = uid ? (names.users?.[uid] ?? null) : null\n const last = pairs[pairs.length - 1]\n const parts: string[] = []\n if (site) parts.push(site)\n else if (org) parts.push(org)\n if (jobPair) parts.push(label ?? pluginStaffAuditCollection(jobPair[0])?.noun ?? 'Job')\n else if (last && last[0] === 'users') parts.push(person ?? 'Account')\n else if (last && last[0] !== 'orgs' && last[0] !== 'hosts') {\n parts.push(\n COLLECTION_NOUNS[last[0]] ??\n pluginStaffAuditCollection(last[0])?.noun ??\n activityTypeLabel(last[0]),\n )\n }\n if (!parts.length) {\n if (last) parts.push(COLLECTION_NOUNS[last[0]] ?? pluginStaffAuditCollection(last[0])?.noun ?? 'Item')\n else if (path) parts.push(isActivityCode(path) || /^[\\w-]{16,}$/.test(path) ? 'Platform' : path)\n else parts.push('—')\n }\n\n const result = str(after['result']) ?? str(after['status']) ?? declared?.result ?? null\n\n return {\n action,\n target: parts.join(' · '),\n why: orgOverrideReasonSummary(entry.reason ?? null, entry.note ?? null) ?? str(entry.note),\n credits: num(after['credits']),\n result,\n code,\n path,\n orgId,\n hostId,\n jobId,\n uid,\n }\n}\n\n/** A stored activity row, read defensively, with where it was read from. */\nexport interface ActivityRowLike {\n action?: string | null\n target?: (ActivityTargetLike & { jobId?: string; credits?: number }) | null\n /** `host` or `org`: which log the row came from. */\n scopeType?: string\n scopeId?: string\n}\n\n/**\n * An activity row as the lists show it. Where is the site when the row came\n * from a site's log, else the organization.\n */\nexport function describeActivity(\n entry: ActivityRowLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const stored = str(entry.action)\n const code = isActivityCode(stored) ? stored : null\n const target = entry.target ?? null\n const hostId = entry.scopeType === 'host' ? (entry.scopeId ?? null) : null\n const orgId = entry.scopeType === 'org' ? (entry.scopeId ?? null) : null\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const type = str(target?.type)\n const jobId = isPluginJobTargetType(type) ? str(target?.id) : str(target?.jobId)\n const noun = type ? (pluginActivityTargetLabel(type) ?? activityTypeLabel(type)) : null\n const item = str(target?.name) ?? noun\n const parts = [site, item].filter((part): part is string => Boolean(part))\n return {\n action: activityActionSentence(stored ?? undefined, target) || '—',\n target: parts.join(' · ') || '—',\n why: null,\n credits: num(target?.credits),\n result: null,\n code,\n path: type ? `${type}${target?.id ? `/${target.id}` : ''}` : null,\n orgId,\n hostId,\n jobId,\n uid: type === 'member' ? str(target?.id) : null,\n }\n}\n"],"names":["activityActionLabel","activityPrimaryText","activityTypeLabel","orgOverrideReasonSummary","isPluginJobTargetType","pluginActivityActionSentence","pluginActivityTargetLabel","pluginStaffAuditActionLabel","pluginStaffAuditCollection","pluginStaffAuditDescribe","activityTargetPhrase","target","noun","type","lower","charAt","toLowerCase","slice","name","trim","activityActionSentence","action","stored","sentence","replace","activityRowText","entry","isActivityCode","test","STAFF_AUDIT_ACTION_LABELS","NAMESPACE_LABELS","org","orgs","user","host","words","segment","humanizeActivityCode","code","head","rest","split","filter","Boolean","lead","c","toUpperCase","length","map","join","staffAuditActionLabel","COLLECTION_NOUNS","hosts","users","members","invites","screens","layouts","components","templates","media","emailDeliveries","coupons","flags","plugins","subscriptions","pathPairs","path","segments","pairs","at","push","str","value","num","Number","isFinite","describeStaffAudit","names","idOf","label","after","includes","collection","find","orgId","jobPair","job","jobId","hostId","uid","subjectUid","declared","undefined","site","person","last","parts","result","why","reason","note","credits","describeActivity","scopeType","scopeId","id","item","part"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;CAiBC,GAED,SACEA,mBAAmB,EACnBC,mBAAmB,EACnBC,iBAAiB,QAGZ,0BAAsB;AAC7B,SAASC,wBAAwB,QAAQ,2BAAuB;AAChE,SACEC,qBAAqB,EACrBC,4BAA4B,EAC5BC,yBAAyB,EACzBC,2BAA2B,EAC3BC,0BAA0B,EAC1BC,wBAAwB,QACnB,+CAA2C;AAsClD,uEAAuE,GACvE,OAAO,SAASC,qBAAqBC,MAA6C;QAGnEA;IAFb,MAAMC,OAAOV,kBAAkBS,0BAAAA,OAAQE,IAAI;IAC3C,MAAMC,QAAQF,SAAS,SAAS,SAASA,KAAKG,MAAM,CAAC,GAAGC,WAAW,KAAKJ,KAAKK,KAAK,CAAC;IACnF,MAAMC,OAAOP,2BAAAA,eAAAA,OAAQO,IAAI,qBAAZP,aAAcQ,IAAI;IAC/B,OAAOD,OAAO,GAAGJ,MAAM,CAAC,EAAEI,MAAM,GAAGJ;AACrC;AAEA;;;;CAIC,GACD,OAAO,SAASM,uBACdC,MAA0B,EAC1BV,MAAkC;;IAElC,MAAMW,iBAASD,0BAAAA,OAAQF,IAAI,qBAAM;IACjC,MAAMI,WAAWlB,6BAA6BiB;IAC9C,IAAIC,UAAU,OAAOA,SAASC,OAAO,CAAC,YAAYd,qBAAqBC;IACvE,OAAOX,oBAAoBsB;AAC7B;AAEA;;;CAGC,GACD,OAAO,SAASG,gBAAgBC,KAAwB;;QACvCA;IAAf,MAAMJ,kBAASI,gBAAAA,MAAML,MAAM,qBAAZK,cAAcP,IAAI,qBAAM;IACvC,OAAOd,6BAA6BiB,UAChCF,uBAAuBE,QAAQI,MAAMf,MAAM,IAC3CV,oBAAoByB;AAC1B;AAEA,qEAAqE,GACrE,OAAO,SAASC,eAAeN,MAAe;IAC5C,OAAO,OAAOA,WAAW,YAAY,4BAA4BO,IAAI,CAACP,OAAOF,IAAI;AACnF;AAEA;;;;;;;4CAO4C,GAC5C,OAAO,MAAMU,4BAA8D;IACzE,uBAAuB;IACvB,yBAAyB;IACzB,qBAAqB;IACrB,gCAAgC;IAChC,oCAAoC;IACpC,sBAAsB;IACtB,kCAAkC;IAClC,0BAA0B;IAC1B,yBAAyB;IACzB,yBAAyB;IACzB,+BAA+B;IAC/B,mBAAmB;IACnB,0BAA0B;IAC1B,0BAA0B;IAC1B,wBAAwB;IACxB,kBAAkB;IAClB,gCAAgC;IAChC,yBAAyB;IACzB,iCAAiC;IACjC,+BAA+B;IAC/B,wCAAwC;IACxC,8BAA8B;IAC9B,iCAAiC;IACjC,gCAAgC;IAChC,iBAAiB;IACjB,2BAA2B;IAC3B,gBAAgB;IAChB,wBAAwB;IACxB,oCAAoC;IACpC,kBAAkB;IAClB,wBAAwB;IACxB,gCAAgC;IAChC,gCAAgC;IAChC,iBAAiB;IACjB,gCAAgC;IAChC,wBAAwB;IACxB,sBAAsB;IACtB,0BAA0B;IAC1B,wBAAwB;IACxB,8BAA8B;IAC9B,6BAA6B;IAC7B,wBAAwB;IACxB,oBAAoB;IACpB,qBAAqB;IACrB,oBAAoB;IACpB,0BAA0B;IAC1B,gBAAgB;IAChB,2BAA2B;IAC3B,gBAAgB;IAChB,wBAAwB;IACxB,wBAAwB;IACxB,sBAAsB;IACtB,iBAAiB;IACjB,iBAAiB;IACjB,0BAA0B;IAC1B,4BAA4B;IAC5B,2BAA2B;IAC3B,4BAA4B;IAC5B,0BAA0B;IAC1B,gCAAgC;IAChC,iCAAiC;IACjC,mCAAmC;IACnC,uBAAuB;IACvB,yBAAyB;IACzB,0BAA0B;IAC1B,gBAAgB;IAChB,sBAAsB;IACtB,uBAAuB;IACvB,4BAA4B;IAC5B,oBAAoB;IACpB,cAAc;IACd,uBAAuB;IACvB,wBAAwB;IACxB,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,gBAAgB;IAChB,cAAc;IACd,0BAA0B;IAC1B,gCAAgC;IAChC,eAAe;IACf,2BAA2B;IAC3B,kCAAkC;IAClC,6BAA6B;IAC7B,yBAAyB;IACzB,+BAA+B;IAC/B,gCAAgC;IAChC,wBAAwB;IACxB,iBAAiB;IACjB,4BAA4B;IAC5B,aAAa;IACb,0BAA0B;IAC1B,6BAA6B;IAC7B,gCAAgC;IAChC,+BAA+B;IAC/B,6BAA6B;IAC7B,6BAA6B;IAC7B,yBAAyB;IACzB,sBAAsB;IACtB,qBAAqB;IACrB,4BAA4B;IAC5B,kBAAkB;IAClB,uBAAuB;IACvB,qBAAqB;IACrB,8BAA8B;IAC9B,6BAA6B;IAC7B,yBAAyB;IACzB,oBAAoB;IACpB,yBAAyB;IACzB,0BAA0B;IAC1B,0BAA0B;IAC1B,2BAA2B;IAC3B,2BAA2B;IAC3B,eAAe;IACf,oBAAoB;IACpB,uBAAuB;IACvB,wBAAwB;IACxB,0BAA0B;IAC1B,4BAA4B;IAC5B,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;AACxB,EAAC;AAED,+CAA+C,GAC/C,MAAMC,mBAAqD;IACzDC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,MAAM;AACR;AAEA,+EAA+E,GAC/E,SAASC,MAAMC,OAAe;IAC5B,OAAOA,QACJZ,OAAO,CAAC,sBAAsB,SAC9BA,OAAO,CAAC,UAAU,KAClBR,WAAW,GACXG,IAAI;AACT;AAEA,qFAAqF,GACrF,OAAO,SAASkB,qBAAqBC,IAAY;QAElCR;IADb,MAAM,CAACS,OAAO,EAAE,EAAE,GAAGC,KAAK,GAAGF,KAAKnB,IAAI,GAAGsB,KAAK,CAAC,KAAKC,MAAM,CAACC;IAC3D,MAAMC,QAAOd,yBAAAA,gBAAgB,CAACS,KAAK,YAAtBT,yBAA0BK,MAAMI,MAAMf,OAAO,CAAC,MAAM,CAACqB,IAAMA,EAAEC,WAAW;IACrF,IAAI,CAACN,KAAKO,MAAM,EAAE,OAAOH;IACzB,OAAO,GAAGA,KAAK,EAAE,EAAEJ,KAAKQ,GAAG,CAACb,OAAOc,IAAI,CAAC,MAAM;AAChD;AAEA,8DAA8D,GAC9D,OAAO,SAASC,sBAAsB7B,MAAiC;cAInEQ,OAAAA;IAHF,MAAMS,eAAOjB,0BAAAA,OAAQF,IAAI,qBAAM;IAC/B,IAAI,CAACmB,MAAM,OAAO;IAClB,QACET,SAAAA,kCAAAA,yBAAyB,CAACS,KAAK,YAA/BT,kCACAtB,4BAA4B+B,iBAD5BT,QAECF,eAAeW,QAAQD,qBAAqBC,QAAQA;AAEzD;AAEA,6DAA6D,GAC7D,MAAMa,mBAAqD;IACzDnB,MAAM;IACNoB,OAAO;IACPC,OAAO;IACPC,SAAS;IACTC,SAAS;IACTC,SAAS;IACTC,SAAS;IACTC,YAAY;IACZC,WAAW;IACXC,OAAO;IACPC,iBAAiB;IACjBC,SAAS;IACTC,OAAO;IACPC,SAAS;IACTC,eAAe;AACjB;AAEA,qEAAqE,GACrE,SAASC,UAAUC,IAAY;IAC7B,MAAMC,WAAWD,KAAK1B,KAAK,CAAC,KAAKC,MAAM,CAACC;IACxC,MAAM0B,QAAiC,EAAE;IACzC,IAAK,IAAIC,KAAK,GAAGA,KAAKF,SAASrB,MAAM,EAAEuB,MAAM,EAAG;YAClCF,cAAoBA;QAAhCC,MAAME,IAAI,CAAC;aAACH,eAAAA,QAAQ,CAACE,GAAG,YAAZF,eAAgB;aAAIA,aAAAA,QAAQ,CAACE,KAAK,EAAE,YAAhBF,aAAoB;SAAG;IACzD;IACA,OAAOC;AACT;AAaA,MAAMG,MAAM,CAACC,QACX,OAAOA,UAAU,YAAYA,MAAMtD,IAAI,KAAKsD,MAAMtD,IAAI,KAAK;AAC7D,MAAMuD,MAAM,CAACD,QACX,OAAOA,UAAU,YAAYE,OAAOC,QAAQ,CAACH,SAASA,QAAQ;AAEhE,iEAAiE,GACjE,OAAO,SAASI,mBACdnD,KAA0B,EAC1BoD,QAAuB,CAAC,CAAC;QAIXpD,cAQC8C,MACHO,kCAcYC,OAeTR,OAAAA,OAAAA,OAKRrE,2BAAyBuB,eAAsBA;QA3B/BoD,cACFA,aACCA,cAKWtE;IAzBjC,MAAM8B,OAAOkC,IAAI9C,MAAML,MAAM;IAC7B,MAAM8C,OAAOK,IAAI9C,MAAMf,MAAM;IAC7B,MAAMsE,SAAQvD,eAAAA,MAAMuD,KAAK,YAAXvD,eAAe,CAAC;IAC9B,MAAM2C,QAAQF,QAAQA,KAAKe,QAAQ,CAAC,OAAOhB,UAAUC,QAAQ,EAAE;IAC/D,MAAMY,OAAO,CAACI;YAAuBd;eAAAA,EAAAA,cAAAA,MAAMe,IAAI,CAAC,CAAC,CAAClE,KAAK,GAAKA,SAASiE,gCAAhCd,WAA6C,CAAC,EAAE,KAAI;;IACzF,MAAMgB,QAAQN,KAAK;IACnB,yEAAyE;IACzE,iEAAiE;IACjE,MAAMO,UAAUjB,MAAMe,IAAI,CAAC,CAAC,CAAClE,KAAK;YAAKV;gBAAAA,8BAAAA,2BAA2BU,0BAA3BV,4BAAkC+E,GAAG;;IAC5E,MAAMC,QAAQF,CAAAA,2BAAAA,OAAS,CAAC,EAAE,KAAI;IAC9B,MAAMG,UAASjB,OAAAA,IAAIS,KAAK,CAAC,SAAS,aAAnBT,OAAwBO,KAAK;IAC5C,MAAMW,OAAMX,QAAAA,KAAK,oBAALA,QAAiBP,IAAI9C,MAAMiE,UAAU;IACjD,MAAMX,QAAQR,IAAIS,KAAK,CAAC,QAAQ;IAChC,wEAAwE;IACxE,uDAAuD;IACvD,MAAMW,WAAWtD,OAAO7B,yBAAyB6B,MAAM2C,SAASY;IAChE,MAAMxE,iBAASuE,4BAAAA,SAAUvE,MAAM,mBAAI6B,sBAAsBZ;IAEzD,MAAMwD,OAAOL,mBAAUX,eAAAA,MAAM1B,KAAK,qBAAX0B,YAAa,CAACW,OAAO,oBAAI,OAAQ;IACxD,MAAM1D,MAAMsD,kBAASP,cAAAA,MAAM9C,IAAI,qBAAV8C,WAAY,CAACO,MAAM,oBAAI,OAAQ;IACpD,MAAMU,SAASL,gBAAOZ,eAAAA,MAAMzB,KAAK,qBAAXyB,YAAa,CAACY,IAAI,oBAAI,OAAQ;IACpD,MAAMM,OAAO3B,KAAK,CAACA,MAAMtB,MAAM,GAAG,EAAE;IACpC,MAAMkD,QAAkB,EAAE;IAC1B,IAAIH,MAAMG,MAAM1B,IAAI,CAACuB;SAChB,IAAI/D,KAAKkE,MAAM1B,IAAI,CAACxC;IACzB,IAAIuD,SAASW,MAAM1B,IAAI,EAACS,QAAAA,gBAAAA,SAASxE,8BAAAA,2BAA2B8E,OAAO,CAAC,EAAE,sBAArC9E,4BAAwCI,IAAI,YAArDoE,QAAyD;SAC5E,IAAIgB,QAAQA,IAAI,CAAC,EAAE,KAAK,SAASC,MAAM1B,IAAI,CAACwB,iBAAAA,SAAU;SACtD,IAAIC,QAAQA,IAAI,CAAC,EAAE,KAAK,UAAUA,IAAI,CAAC,EAAE,KAAK,SAAS;YAExD7C,OAAAA;YACE3C;QAFJyF,MAAM1B,IAAI,EACRpB,SAAAA,0BAAAA,gBAAgB,CAAC6C,IAAI,CAAC,EAAE,CAAC,YAAzB7C,2BACE3C,+BAAAA,2BAA2BwF,IAAI,CAAC,EAAE,sBAAlCxF,6BAAqCI,IAAI,YAD3CuC,QAEEjD,kBAAkB8F,IAAI,CAAC,EAAE;IAE/B;IACA,IAAI,CAACC,MAAMlD,MAAM,EAAE;YACII,OAAAA;YAA6B3C;QAAlD,IAAIwF,MAAMC,MAAM1B,IAAI,EAACpB,SAAAA,2BAAAA,gBAAgB,CAAC6C,IAAI,CAAC,EAAE,CAAC,YAAzB7C,4BAA6B3C,+BAAAA,2BAA2BwF,IAAI,CAAC,EAAE,sBAAlCxF,6BAAqCI,IAAI,YAAtEuC,QAA0E;aAC1F,IAAIgB,MAAM8B,MAAM1B,IAAI,CAAC5C,eAAewC,SAAS,eAAevC,IAAI,CAACuC,QAAQ,aAAaA;aACtF8B,MAAM1B,IAAI,CAAC;IAClB;IAEA,MAAM2B,UAAS1B,SAAAA,SAAAA,QAAAA,IAAIS,KAAK,CAAC,SAAS,aAAnBT,QAAwBA,IAAIS,KAAK,CAAC,SAAS,aAA3CT,QAAgDoB,4BAAAA,SAAUM,MAAM,YAAhE1B,QAAoE;IAEnF,OAAO;QACLnD;QACAV,QAAQsF,MAAMhD,IAAI,CAAC;QACnBkD,GAAG,GAAEhG,4BAAAA,0BAAyBuB,gBAAAA,MAAM0E,MAAM,YAAZ1E,gBAAgB,OAAMA,cAAAA,MAAM2E,IAAI,YAAV3E,cAAc,iBAA7DvB,4BAAsEqE,IAAI9C,MAAM2E,IAAI;QACzFC,SAAS5B,IAAIO,KAAK,CAAC,UAAU;QAC7BiB;QACA5D;QACA6B;QACAkB;QACAI;QACAD;QACAE;IACF;AACF;AAWA;;;CAGC,GACD,OAAO,SAASa,iBACd7E,KAAsB,EACtBoD,QAAuB,CAAC,CAAC;QAIVpD,eAC8BA,gBACFA,uBAItBpB,4BACRkE;QAJUM;IALvB,MAAMxD,SAASkD,IAAI9C,MAAML,MAAM;IAC/B,MAAMiB,OAAOX,eAAeL,UAAUA,SAAS;IAC/C,MAAMX,UAASe,gBAAAA,MAAMf,MAAM,YAAZe,gBAAgB;IAC/B,MAAM+D,SAAS/D,MAAM8E,SAAS,KAAK,UAAU9E,iBAAAA,MAAM+E,OAAO,YAAb/E,iBAAiB,OAAQ;IACtE,MAAM2D,QAAQ3D,MAAM8E,SAAS,KAAK,SAAS9E,kBAAAA,MAAM+E,OAAO,YAAb/E,kBAAiB,OAAQ;IACpE,MAAMoE,OAAOL,kBAAUX,eAAAA,MAAM1B,KAAK,qBAAX0B,YAAa,CAACW,OAAO,mBAAI,OAAQ;IACxD,MAAM5E,OAAO2D,IAAI7D,0BAAAA,OAAQE,IAAI;IAC7B,MAAM2E,QAAQpF,sBAAsBS,QAAQ2D,IAAI7D,0BAAAA,OAAQ+F,EAAE,IAAIlC,IAAI7D,0BAAAA,OAAQ6E,KAAK;IAC/E,MAAM5E,OAAOC,QAAQP,6BAAAA,0BAA0BO,iBAA1BP,6BAAmCJ,kBAAkBW,QAAS;IACnF,MAAM8F,QAAOnC,OAAAA,IAAI7D,0BAAAA,OAAQO,IAAI,aAAhBsD,OAAqB5D;IAClC,MAAMqF,QAAQ;QAACH;QAAMa;KAAK,CAACjE,MAAM,CAAC,CAACkE,OAAyBjE,QAAQiE;IACpE,OAAO;QACLvF,QAAQD,uBAAuBE,iBAAAA,SAAUuE,WAAWlF,WAAW;QAC/DA,QAAQsF,MAAMhD,IAAI,CAAC,UAAU;QAC7BkD,KAAK;QACLG,SAAS5B,IAAI/D,0BAAAA,OAAQ2F,OAAO;QAC5BJ,QAAQ;QACR5D;QACA6B,MAAMtD,OAAO,GAAGA,OAAOF,CAAAA,0BAAAA,OAAQ+F,EAAE,IAAG,CAAC,CAAC,EAAE/F,OAAO+F,EAAE,EAAE,GAAG,IAAI,GAAG;QAC7DrB;QACAI;QACAD;QACAE,KAAK7E,SAAS,WAAW2D,IAAI7D,0BAAAA,OAAQ+F,EAAE,IAAI;IAC7C;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-labels.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 * ONE LABEL MAP FOR EVERY ACTIVITY AND AUDIT LIST (AGL-3660).\n *\n * Two logs feed the console's lists. The activity logs\n * (`orgs/{id}/activity`, `hosts/{id}/activity`) store a prose sentence or a\n * plugin's code plus a `{ type, id, name }` target. The staff audit trail\n * (`adminAudit`) stores a dotted code (`ai.job.output`, `org.override`) and\n * a Firestore PATH as its target (`orgs/abc/aiJobs/xyz`). The staff tables\n * printed both raw, so a reader saw `ai.job.output` next to\n * `orgs/9fK…/aiJobs/Qe2…` and had to know the schema to read their own\n * audit trail.\n *\n * Every list now draws its words from here: an action as a sentence\n * (`Created page Home with Aglyn AI`), a target as the site and the item,\n * and — for an audit row — why, the credits it spent and what came of it.\n * The raw code and path are kept on the description, for a tooltip and for\n * the staff details dialog, and never as the visible text.\n */\n\nimport {\n activityActionLabel,\n activityPrimaryText,\n activityTypeLabel,\n type ActivityEntryLike,\n type ActivityTargetLike,\n} from './activity-presenter'\nimport { orgOverrideReasonSummary } from './org-override-reason'\nimport {\n isPluginJobTargetType,\n pluginActivityActionSentence,\n pluginActivityTargetLabel,\n pluginStaffAuditActionLabel,\n pluginStaffAuditCollection,\n pluginStaffAuditDescribe,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** What a list row and the details dialog show for one entry. */\nexport interface ActivityDescription {\n /** The act, as a sentence. Never a code. */\n action: string\n /** What it was done to, in words: the site and the item. Never a path. */\n target: string\n /** Why it was done, when the writer recorded a reason. */\n why: string | null\n /** AI credits it spent, when it spent any. */\n credits: number | null\n /** What came of it, when the writer recorded an outcome. */\n result: string | null\n /** The stored code, when the action was one — for a tooltip and staff. */\n code: string | null\n /** The stored target path (audit) or `type/id` (activity) — staff only. */\n path: string | null\n /** The organization the entry concerns, when it names one. */\n orgId: string | null\n /** The site the entry concerns, when it names one. */\n hostId: string | null\n /** The plugin job behind the entry (an AI job), when there is one. */\n jobId: string | null\n /** The account the entry concerns, when it names one. */\n uid: string | null\n}\n\n/** Names a route resolved for the ids an entry carries. */\nexport interface ActivityNames {\n /** Organization name by org id. */\n orgs?: Readonly<Record<string, string | null | undefined>>\n /** Site name by host id. */\n hosts?: Readonly<Record<string, string | null | undefined>>\n /** An account's address (or name) by uid. */\n users?: Readonly<Record<string, string | null | undefined>>\n}\n\n/** `screen` → `page`, `Home` → `page Home`: the noun a sentence uses. */\nexport function activityTargetPhrase(target: ActivityTargetLike | null | undefined): string {\n const noun = activityTypeLabel(target?.type)\n const lower = noun === 'Item' ? 'item' : noun.charAt(0).toLowerCase() + noun.slice(1)\n const name = target?.name?.trim()\n return name ? `${lower} ${name}` : lower\n}\n\n/**\n * An activity row's action as a sentence about its target. A plugin code\n * with a declared sentence fills it (`Created {target} with Aglyn AI`); any\n * other code reads as its label, and a prose action as itself.\n */\nexport function activityActionSentence(\n action: string | undefined,\n target?: ActivityTargetLike | null,\n): string {\n const stored = action?.trim() ?? ''\n const sentence = pluginActivityActionSentence(stored)\n if (sentence) return sentence.replace('{target}', activityTargetPhrase(target))\n return activityActionLabel(stored)\n}\n\n/**\n * A one-column feed's line: the sentence when the code declares one, else\n * the action with its target's name (`Saved the screen — Home`).\n */\nexport function activityRowText(entry: ActivityEntryLike): string {\n const stored = entry.action?.trim() ?? ''\n return pluginActivityActionSentence(stored)\n ? activityActionSentence(stored, entry.target)\n : activityPrimaryText(entry)\n}\n\n/** Whether a stored action is a dotted code rather than a sentence. */\nexport function isActivityCode(action: unknown): action is string {\n return typeof action === 'string' && /^[a-z][\\w-]*(\\.[\\w-]+)+$/i.test(action.trim())\n}\n\n/*==========================================\n * THE STAFF AUDIT TRAIL'S WORDS.\n *\n * Every code core's doors write. A plugin's doors declare their own through\n * `staffAuditLabels` (the AI plugin's `ai.job.*`), and a code neither knows\n * is humanized from its segments rather than shown raw, so a new writer\n * reads as `Billing: invoice paid` until somebody names it properly.\n *==========================================*/\nexport const STAFF_AUDIT_ACTION_LABELS: Readonly<Record<string, string>> = {\n 'account.closed.self': 'Closed their own account',\n 'account.exported.self': 'Exported their own account data',\n 'ai-usage.exported': 'Exported AI usage',\n 'billing.assistOverage.setCap': 'Set the AI overage ceiling',\n 'billing.assistOverage.setHardCap': 'Set the AI stop-at-band switch',\n 'billing.autoLocked': 'Locked automatically for billing',\n 'billing.bundle-link-minted': 'Made a bundle checkout link',\n 'billing.collaboratorAllocation': 'Changed collaborator seats',\n 'billing.dispute.opened': 'Payment dispute opened',\n 'billing.disputeClosed': 'Payment dispute closed',\n 'billing.disputeOpened': 'Payment dispute opened',\n 'billing.disputeUnattributed': 'Unattributed payment dispute',\n 'billing.invoice': 'Invoice event',\n 'billing.invoice.closed': 'Invoice closed',\n 'billing.invoice.failed': 'Invoice payment failed',\n 'billing.invoice.paid': 'Invoice paid',\n 'billing.manage': 'Opened billing management',\n 'billing.orphanedSubscription': 'Found a subscription with no workspace',\n 'billing.paymentFailed': 'Payment failed',\n 'billing.paymentMethod.changed': 'Changed the payment method',\n 'billing.platformDisputeLost': 'Lost a payment dispute',\n 'billing.platformInvoiceUncollectible': 'Invoice marked uncollectible',\n 'billing.registerAllocation': 'Changed register allocation',\n 'billing.storageOverage.setCap': 'Set the storage overage ceiling',\n 'billing.subscriptionCanceled': 'Subscription canceled',\n 'billing.usage': 'Recorded usage',\n 'billing.usageBudget.set': 'Set the usage budget',\n 'billing.view': 'Viewed billing',\n 'billing.webhookInert': 'Ignored a billing webhook',\n 'billing.webhookSignatureRejected': 'Rejected a billing webhook signature',\n 'broadcast.send': 'Sent a broadcast',\n 'client-build.preview-sent': 'Sent a client build preview',\n 'contact.phone.erased': 'Erased a phone number',\n 'contact.suppression.recorded': 'Added a contact suppression',\n 'contact.suppression.released': 'Released a contact suppression',\n 'coupon.create': 'Created a coupon',\n 'coupon.promotion_code.update': 'Updated a promotion code',\n 'data.transfer.export': 'Exported data',\n 'data.transfer.plan': 'Planned a data transfer',\n 'email.history-imported': 'Imported email history',\n 'email.message-viewed': 'Viewed an email message',\n 'email.sending-domains.reap': 'Removed unused sending domains',\n 'email.suppression.release': 'Released an email suppression',\n 'emailSendRate.update': 'Changed the email send rate',\n 'erasure.runBatch': 'Ran an erasure batch',\n 'erasures.replayed': 'Replayed erasures',\n 'firestore.export': 'Exported the database',\n 'firstPartyHosts.update': 'Changed first-party sites',\n 'flags.update': 'Changed feature flags',\n 'freeWorkspaceCap.update': 'Changed the free workspace cap',\n 'host.deleted': 'Deleted a site',\n 'host.domain.released': 'Released a domain',\n 'host.reattach-domain': 'Reattached a domain',\n 'host.set-subdomain': 'Changed a site address',\n 'host.transfer': 'Transferred a site',\n 'lockdown.lock': 'Locked the workspace down',\n 'lockdown.payouts-pause': 'Paused payouts',\n 'lockdown.payouts-restore': 'Restored payouts',\n 'lockdown.renewals-pause': 'Paused renewals',\n 'lockdown.renewals-resume': 'Resumed renewals',\n 'lockdown.resend-notice': 'Resent the lockdown notice',\n 'maintenance.auditArchive.run': 'Archived the audit log',\n 'maintenance.reapArtifacts.run': 'Removed stale build artifacts',\n 'maintenance.reverifyPlugins.run': 'Re-verified plugins',\n 'operatorAlerts.test': 'Sent a test operator alert',\n 'operatorAlerts.update': 'Changed operator alerts',\n 'org.acquisition-viewed': 'Viewed where the organization came from',\n 'org.auditLog': 'Viewed the organization audit log',\n 'org.discount.apply': 'Applied a discount',\n 'org.discount.remove': 'Removed a discount',\n 'org.enterprise.provision': 'Provisioned enterprise billing',\n 'org.erase-failed': 'Organization erasure failed',\n 'org.erased': 'Erased the organization',\n 'org.erasureCanceled': 'Canceled the organization erasure',\n 'org.erasureRequested': 'Requested organization erasure',\n 'org.exported': 'Exported the organization',\n 'org.name': 'Renamed the organization',\n 'org.note': 'Added a staff note',\n 'org.override': 'Overrode the plan',\n 'org.refund': 'Issued a refund',\n 'org.seatAddons.changed': 'Changed seat add-ons',\n 'org.sso.enforceSignInMethods': 'Changed enforced sign-in methods',\n 'org.sso.jit': 'Changed single sign-on provisioning',\n 'org.subscription-cancel': 'Canceled the subscription',\n 'org.subscriptionStarted.settle': 'Settled a new subscription',\n 'org.successManagerCleared': 'Removed the success manager',\n 'org.successManagerSet': 'Assigned a success manager',\n 'org.upgradeProposal.propose': 'Proposed an upgrade',\n 'org.upgradeProposal.withdraw': 'Withdrew an upgrade proposal',\n 'orgs.unverified.reap': 'Removed unverified organizations',\n 'person.erased': 'Erased a person',\n 'person.erasure-requested': 'Requested erasure of a person',\n 'plan.name': 'Renamed a plan',\n 'plugins.artifacts.reap': 'Removed stale plugin builds',\n 'plugins.remoteServer.load': 'Loaded a remote plugin server',\n 'plugins.verification.decline': 'Declined a plugin verification',\n 'plugins.verifier.regression': 'Plugin verifier regression',\n 'security.pageSecurityHold': 'Put a page on security hold',\n 'security.paymentFraudHold': 'Put a workspace on hold for payment fraud',\n 'security.staffGranted': 'Granted staff access',\n 'site.package.apply': 'Applied a site package',\n 'site.package.undo': 'Undid a site package',\n 'sso.domain.driftDetected': 'Single sign-on domain drift detected',\n 'staff.evidence': 'Recorded evidence',\n 'staff.paymentFailed': 'Payment failed (staff notice)',\n 'staff.planChanged': 'Plan changed (staff notice)',\n 'staff.subscriptionCanceled': 'Subscription canceled (staff notice)',\n 'staff.subscriptionStarted': 'Subscription started (staff notice)',\n 'systemEmail.staffSend': 'Sent a system email',\n 'systemEmail.test': 'Sent a test system email',\n 'taxFilingConfig.clear': 'Cleared the tax filing settings',\n 'taxFilingConfig.update': 'Changed the tax filing settings',\n 'taxablePurchases.clear': 'Cleared taxable purchases',\n 'taxablePurchases.update': 'Changed taxable purchases',\n 'user.acquisition-viewed': 'Viewed where the account came from',\n 'user.erased': 'Erased the account',\n 'user.impersonate': 'Signed in as the account',\n 'user.nativeAppCheck': 'Verified the desktop app for the account',\n 'user.nativeAppSignIn': 'Signed in to the app through the console',\n 'user.sendPasswordReset': 'Sent a password reset',\n 'user.setAdAudienceOptOut': 'Changed the ad audience opt-out',\n 'user.setPassword': 'Set the password',\n 'user.signOutDevice': 'Signed a device out',\n 'user.updateProfile': 'Updated the profile',\n}\n\n/** Leading namespaces as a reader names them. */\nconst NAMESPACE_LABELS: Readonly<Record<string, string>> = {\n org: 'Organization',\n orgs: 'Organizations',\n user: 'Account',\n host: 'Site',\n}\n\n/** `paymentMethod` → `payment method`, `reattach-domain` → `reattach domain`. */\nfunction words(segment: string): string {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n}\n\n/** A code nobody named, as words: `billing.invoice.paid` → `Billing: invoice paid`. */\nexport function humanizeActivityCode(code: string): string {\n const [head = '', ...rest] = code.trim().split('.').filter(Boolean)\n const lead = NAMESPACE_LABELS[head] ?? words(head).replace(/^./, (c) => c.toUpperCase())\n if (!rest.length) return lead\n return `${lead}: ${rest.map(words).join(' ')}`\n}\n\n/** What a staff audit code reads as, everywhere it is shown. */\nexport function staffAuditActionLabel(action: string | null | undefined): string {\n const code = action?.trim() ?? ''\n if (!code) return '—'\n return (\n STAFF_AUDIT_ACTION_LABELS[code] ??\n pluginStaffAuditActionLabel(code) ??\n (isActivityCode(code) ? humanizeActivityCode(code) : code)\n )\n}\n\n/** The collections an audit path walks, named for a reader. */\nconst COLLECTION_NOUNS: Readonly<Record<string, string>> = {\n orgs: 'Organization',\n hosts: 'Site',\n users: 'Account',\n members: 'Member',\n invites: 'Invitation',\n screens: 'Page',\n layouts: 'Layout',\n components: 'Component',\n templates: 'Template',\n media: 'Media',\n emailDeliveries: 'Email',\n coupons: 'Coupon',\n flags: 'Feature flag',\n plugins: 'Plugin',\n subscriptions: 'Subscription',\n}\n\n/** `orgs/a/aiJobs/b` → `{ orgs: 'a', aiJobs: 'b' }`, in walk order. */\nfunction pathPairs(path: string): Array<[string, string]> {\n const segments = path.split('/').filter(Boolean)\n const pairs: Array<[string, string]> = []\n for (let at = 0; at < segments.length; at += 2) {\n pairs.push([segments[at] ?? '', segments[at + 1] ?? ''])\n }\n return pairs\n}\n\n/** The stored audit row, read defensively. */\nexport interface StaffAuditEntryLike {\n action?: string | null\n target?: string | null\n subjectUid?: string | null\n reason?: string | null\n note?: string | null\n /** The state the act left; AI rows carry `{ label, hostId, credits }`. */\n after?: Record<string, unknown> | null\n}\n\nconst str = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\nconst num = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** What an audit act did, in words, and the words it was about. */\nexport function describeStaffAudit(\n entry: StaffAuditEntryLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const code = str(entry.action)\n const path = str(entry.target)\n const after = entry.after ?? {}\n const pairs = path && path.includes('/') ? pathPairs(path) : []\n const idOf = (collection: string) => pairs.find(([name]) => name === collection)?.[1] || null\n const orgId = idOf('orgs')\n // A plugin's job collection (the AI plugin's jobs) names the job the row\n // links to; core knows it only through the plugin's declaration.\n const jobPair = pairs.find(([name]) => pluginStaffAuditCollection(name)?.job)\n const jobId = jobPair?.[1] || null\n const hostId = str(after['hostId']) ?? idOf('hosts')\n const uid = idOf('users') ?? str(entry.subjectUid)\n const label = str(after['label'])\n // A plugin may word the row from what the act left: the AI plugin names\n // what a job made (`Created page Home with Aglyn AI`).\n const declared = code ? pluginStaffAuditDescribe(code, after) : undefined\n const action = declared?.action ?? staffAuditActionLabel(code)\n\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const org = orgId ? (names.orgs?.[orgId] ?? null) : null\n const person = uid ? (names.users?.[uid] ?? null) : null\n const last = pairs[pairs.length - 1]\n const parts: string[] = []\n if (site) parts.push(site)\n else if (org) parts.push(org)\n if (jobPair) parts.push(label ?? pluginStaffAuditCollection(jobPair[0])?.noun ?? 'Job')\n else if (last && last[0] === 'users') parts.push(person ?? 'Account')\n else if (last && last[0] !== 'orgs' && last[0] !== 'hosts') {\n parts.push(\n COLLECTION_NOUNS[last[0]] ??\n pluginStaffAuditCollection(last[0])?.noun ??\n activityTypeLabel(last[0]),\n )\n }\n if (!parts.length) {\n if (last) parts.push(COLLECTION_NOUNS[last[0]] ?? pluginStaffAuditCollection(last[0])?.noun ?? 'Item')\n else if (path) parts.push(isActivityCode(path) || /^[\\w-]{16,}$/.test(path) ? 'Platform' : path)\n else parts.push('—')\n }\n\n const result = str(after['result']) ?? str(after['status']) ?? declared?.result ?? null\n\n return {\n action,\n target: parts.join(' · '),\n why: orgOverrideReasonSummary(entry.reason ?? null, entry.note ?? null) ?? str(entry.note),\n credits: num(after['credits']),\n result,\n code,\n path,\n orgId,\n hostId,\n jobId,\n uid,\n }\n}\n\n/** A stored activity row, read defensively, with where it was read from. */\nexport interface ActivityRowLike {\n action?: string | null\n target?: (ActivityTargetLike & { jobId?: string; credits?: number }) | null\n /** `host` or `org`: which log the row came from. */\n scopeType?: string\n scopeId?: string\n}\n\n/**\n * An activity row as the lists show it. Where is the site when the row came\n * from a site's log, else the organization.\n */\nexport function describeActivity(\n entry: ActivityRowLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const stored = str(entry.action)\n const code = isActivityCode(stored) ? stored : null\n const target = entry.target ?? null\n const hostId = entry.scopeType === 'host' ? (entry.scopeId ?? null) : null\n const orgId = entry.scopeType === 'org' ? (entry.scopeId ?? null) : null\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const type = str(target?.type)\n const jobId = isPluginJobTargetType(type) ? str(target?.id) : str(target?.jobId)\n const noun = type ? (pluginActivityTargetLabel(type) ?? activityTypeLabel(type)) : null\n const item = str(target?.name) ?? noun\n const parts = [site, item].filter((part): part is string => Boolean(part))\n return {\n action: activityActionSentence(stored ?? undefined, target) || '—',\n target: parts.join(' · ') || '—',\n why: null,\n credits: num(target?.credits),\n result: null,\n code,\n path: type ? `${type}${target?.id ? `/${target.id}` : ''}` : null,\n orgId,\n hostId,\n jobId,\n uid: type === 'member' ? str(target?.id) : null,\n }\n}\n"],"names":["activityActionLabel","activityPrimaryText","activityTypeLabel","orgOverrideReasonSummary","isPluginJobTargetType","pluginActivityActionSentence","pluginActivityTargetLabel","pluginStaffAuditActionLabel","pluginStaffAuditCollection","pluginStaffAuditDescribe","activityTargetPhrase","target","noun","type","lower","charAt","toLowerCase","slice","name","trim","activityActionSentence","action","stored","sentence","replace","activityRowText","entry","isActivityCode","test","STAFF_AUDIT_ACTION_LABELS","NAMESPACE_LABELS","org","orgs","user","host","words","segment","humanizeActivityCode","code","head","rest","split","filter","Boolean","lead","c","toUpperCase","length","map","join","staffAuditActionLabel","COLLECTION_NOUNS","hosts","users","members","invites","screens","layouts","components","templates","media","emailDeliveries","coupons","flags","plugins","subscriptions","pathPairs","path","segments","pairs","at","push","str","value","num","Number","isFinite","describeStaffAudit","names","idOf","label","after","includes","collection","find","orgId","jobPair","job","jobId","hostId","uid","subjectUid","declared","undefined","site","person","last","parts","result","why","reason","note","credits","describeActivity","scopeType","scopeId","id","item","part"],"mappings":"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"}
@@ -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 AI.
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
@@ -188,6 +189,19 @@ export interface AnalyticsEventParams {
188
189
  step: string;
189
190
  area: string;
190
191
  };
192
+ /** Custom (AGL-3979). A step's "More details" was opened; `step` as above. */
193
+ site_start_more_details: {
194
+ step: string;
195
+ };
196
+ /**
197
+ * Custom (AGL-3979). A site was asked for with an optional detail given:
198
+ * one event per detail, `field` its id (`phone`, `email`, `hours`…) and
199
+ * `step` the step that asks it. Never the value.
200
+ */
201
+ site_start_detail_given: {
202
+ step: string;
203
+ field: string;
204
+ };
191
205
  /** Custom: no GA4 equivalent. Stripe Connect onboarding completed. */
192
206
  stripe_connected: Record<string, never>;
193
207
  /** GA4 recommended. A plan checkout started. */