@coolms/core-angular 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +16 -15
- package/fesm2022/coolms-core-angular.mjs +1971 -2008
- package/fesm2022/coolms-core-angular.mjs.map +1 -1
- package/package.json +4 -1
- package/types/coolms-core-angular.d.ts +1593 -995
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coolms-core-angular.mjs","sources":["../../src/api/api-manifest.types.ts","../../src/api/form-render.types.ts","../../src/state/app-config.state.ts","../../src/api/identity-api.client.ts","../../src/api/realtime-token.client.ts","../../src/auth/auth.actions.ts","../../src/services/user-preferences.service.ts","../../src/auth/auth.state.ts","../../src/auth/auth-refresh.coordinator.ts","../../src/auth/auth.interceptor.ts","../../src/auth/cross-tab-auth-sync.service.ts","../../src/bootstrap/app-init.service.ts","../../src/auth/auth.guard.ts","../../src/auth/login-page.guard.ts","../../src/auth/login/login.component.ts","../../src/auth/login/login.component.html","../../src/config/config.service.ts","../../src/errors/error-handler.service.ts","../../src/theme/theme.service.ts","../../src/loader/cms-loader.component.ts","../../src/sidebar/sidebar-state.service.ts","../../src/entity-search/entity-search.service.ts","../../src/navi-graph/navi-graph.service.ts","../../src/navi-graph/navi-graph.types.ts","../../src/navi-graph/component-registry.ts","../../src/section/current-section.port.ts","../../src/interceptors/section.interceptor.ts","../../src/public-api.ts","../../src/coolms-core-angular.ts"],"sourcesContent":["// Typed contract — must mirror ApiManifest PHP VOs exactly.\n// No `any` anywhere.\n\nexport interface AuthApiManifest {\n readonly login: string;\n readonly logout: string;\n readonly refresh: string;\n readonly register: string;\n readonly me: string;\n readonly usersApi?: string; // GET /api/v1/auth/users\n readonly groupsApi?: string; // GET /api/v1/auth/groups\n readonly preferencesUrl?: string; // PATCH /api/v1/auth/me/preferences\n readonly groupFormId?: string | null;\n readonly userFormId?: string | null;\n}\n\nexport interface SectionApiManifest {\n readonly list: string;\n readonly create: string;\n readonly item: string; // URL pattern: /api/v1/sections/{id}\n readonly update: string;\n readonly delete: string;\n readonly formId?: string;\n readonly apply?: string; // POST /api/v1/sections/_apply (H9)\n}\n\nexport interface NaviApiManifest {\n readonly treesList: string;\n readonly treesCreate: string;\n readonly treesItem: string; // pattern: /api/v1/navi/trees/{slug}\n readonly nodesList: string;\n readonly nodesCreate: string;\n readonly nodesItem: string; // pattern: /api/v1/navi/nodes/{id}\n readonly nodesReorder: string; // PATCH /api/v1/navi/nodes/reorder\n readonly nodesMove: string; // pattern: /api/v1/navi/nodes/{id}/move\n readonly nodesByTree: string; // pattern: /api/v1/navi/trees/{slug}/nodes\n readonly treesFormId?: string;\n readonly nodesFormId?: string;\n readonly adminNavGraph?: string; // /api/v1/navi/trees/navi.admin/graph\n readonly topbarNavGraph?: string; // /api/v1/navi/trees/navi.admin.topbar/graph\n readonly toolbarVfsGraph?: string; // /api/v1/navi/trees/navi.toolbar.vfs/graph\n readonly graphBySlug?: string; // URL pattern: /api/v1/navi/trees/{slug}/graph\n}\n\nexport interface ContentApiManifest {\n // Page + variant CRUD lives on the generic VFS node endpoints, filtered\n // by mime where needed; what stays here is Content-owned surface.\n readonly variantPublishUrl: string; // pattern: /api/v1/content/variants/{id}/publish\n // Pages admin. Optional because a client built against an older backend\n // will not receive them, and the affected control degrades (an empty\n // space list, a picker with only \"Default\") rather than throwing.\n //\n // The five `articleXxxUrl` fields that stood here are GONE with the\n // endpoints they named. Placement destinations now\n // come from `/content/pages/surfaces` — still never hardcoded on the\n // client, because a surface is configuration, not a product concept.\n readonly pageSpacesUrl?: string; // /api/v1/content/pages/spaces\n readonly pageTypesUrl?: string; // /api/v1/content/page-types\n}\n\nexport interface DataGridApiManifest {\n readonly configBase: string; // base URL: /api/v1/datagrids\n}\n\nexport interface TerminalApiManifest {\n readonly executeUrl: string; // POST /api/v1/terminal/execute (SSE)\n readonly completeUrl: string; // POST /api/v1/terminal/complete\n}\n\nexport interface MediaApiManifest {\n readonly listUrl: string; // GET /media\n readonly itemUrl: string; // pattern: /api/v1/media/{id}\n readonly permissionsUrl: string; // pattern: /api/v1/media/{id}/permissions\n readonly regenerateUrl: string; // pattern: /api/v1/media/{id}/regenerate\n readonly resizeUrl: string; // pattern: /media/resize/{id}\n readonly collectionsUrl: string; // GET /api/v1/media/collections\n readonly collectionsCreateUrl: string; // POST /api/v1/media/collections\n readonly collectionPermissionsUrl: string; // PATCH /api/v1/media/collections/permissions\n readonly moveUrl?: string; // pattern: /api/v1/media/{id}/move\n readonly spacesUrl?: string; // GET /api/v1/media/spaces\n}\n\nexport interface VfsApiManifest {\n readonly fileContentUrl: string; // GET/PUT /api/v1/vfs/files/content?path={path}\n readonly binaryWriteUrl: string; // POST /api/v1/vfs/files/binary (multipart)\n}\n\n/**\n * Document module URL section. Today the only entry is `spacesUrl`;\n * the struct is reserved so future Document-module URLs can land\n * here without a new manifest section.\n */\nexport interface DocumentApiManifest {\n readonly spacesUrl?: string; // GET /api/v1/document/spaces\n}\n\nexport interface DynamicEntityApiManifest {\n readonly typesUrl: string; // GET /api/v1/dynamic-entity/types\n readonly fieldDefinitionsUrl: string; // POST /api/v1/field/definitions\n readonly fieldDefinitionUrl: string; // PUT/PATCH/DELETE /api/v1/field/definitions/{id}\n readonly fieldDefinitionsReorderUrl: string; // PATCH /api/v1/field/definitions/reorder\n readonly toolbarNaviGraphUrl: string; // GET /navi/trees/navi.toolbar.dynamic-entity/graph\n readonly constraintsUrl: string; // GET /api/v1/dynamic-entity/constraints\n readonly recordsUrl: string; // pattern: GET/POST /api/v1/dynamic-entity/{type}/records\n readonly recordUrl: string; // pattern: GET/PATCH/DELETE .../records/{id}\n readonly recordsByTypeUrl: string; // pattern: GET/POST /api/v1/dynamic-entity/{type}/records\n readonly typeUrl: string; // GET/PATCH/DELETE .../types/{id}\n readonly typesCreateUrl: string; // POST .../types (same as typesUrl)\n readonly formTypesUrl?: string; // GET /api/v1/field/form-types\n readonly typeByAliasUrl?: string; // GET /api/v1/dynamic-entity/types/{alias}\n}\n\nexport interface DomainExplorerApiManifest {\n readonly entitiesUrl: string; // GET /api/v1/domain-explorer/entities\n readonly entityUrl: string; // pattern: /api/v1/domain-explorer/entities/{entityAlias}\n readonly toolbarNaviGraphUrl: string; // GET /navi/trees/navi.toolbar.domain_explorer/graph\n readonly domainExplorerToolbarUrl: string; // alias for toolbarNaviGraphUrl (backward compat)\n readonly entityFieldsDataGridUrl?: string; // GET /api/v1/datagrids/domain_explorer:entity_fields\n readonly typeFieldsDataGridUrl?: string; // GET /api/v1/datagrids/domain_explorer:type_fields\n}\n\nexport interface IdentityApiManifest {\n readonly usersUrl: string; // GET /auth/users\n readonly userUrl: string; // /auth/users/{id} pattern\n readonly groupsUrl: string; // GET /auth/groups\n readonly groupUrl: string; // /auth/groups/{id} pattern\n readonly meUrl: string; // GET /auth/me\n readonly assignGroupsUrl: string; // /auth/users/{id}/groups pattern\n readonly avatarUploadUrl: string; // POST /auth/me/avatar\n readonly settingsUrl: string; // GET /auth/me/settings\n readonly settingsSectionsUrl: string; // GET /auth/me/settings/sections\n readonly settingsSectionUrl: string; // PATCH /auth/me/settings/{section} pattern\n readonly colorUrl: string; // PATCH /auth/me/color\n readonly rolesUrl?: string; // GET /api/v1/identity/roles\n}\n\n/**\n * Editor sub-manifest emitted by the backend's Editor module. The\n * frontend bridge (`@coolms/editor-angular`) consumes this through the\n * `EDITOR_MANIFEST_PROVIDER` token.\n *\n * schemaVersion 2 (sub-prompt D) replaced the editorId axis with a profile\n * axis: `manifest.editor.profiles[name]` returns a slice with the\n * pre-resolved contributor list AND the storage allow-list a sanitizer\n * enforces on save.\n */\nexport interface EditorApiManifestEntry {\n readonly id: string;\n readonly group: string;\n readonly priority: number;\n readonly icon: string;\n readonly label: string;\n readonly actionType: string;\n readonly actionParams: Readonly<Record<string, unknown>>;\n readonly extensions: ReadonlyArray<string>;\n readonly stateKeys: ReadonlyArray<string>;\n readonly shortcut?: string | null;\n // `slashable` (derived from `group`) flags the\n // entries the `/`-command palette offers; `keywords` are extra fuzzy-search\n // aliases. Both serialized into `GET /api/v1/theme/config`.\n readonly slashable?: boolean;\n readonly keywords?: ReadonlyArray<string>;\n}\n\nexport interface EditorApiManifestProfile {\n readonly contributors: ReadonlyArray<EditorApiManifestEntry>;\n readonly allowedWidgets: ReadonlyArray<string>;\n}\n\nexport interface EditorApiManifest {\n readonly schemaVersion: number;\n readonly profiles: Readonly<Record<string, EditorApiManifestProfile>>;\n}\n\n/**\n * Viewer manifest emitted by the backend's Document module.\n * Each viewer declares the MIME types / extensions it handles, the\n * Angular component selector to dispatch into, and one config blob per\n * profile. Profile config is open-ended — every viewer interprets the\n * keys it understands and ignores the rest.\n */\nexport interface ViewerProfileApiManifest {\n readonly key: string;\n readonly config: Readonly<Record<string, unknown>>;\n}\n\nexport interface ViewerDefinitionApiManifest {\n readonly key: string;\n readonly mimeTypes: ReadonlyArray<string>;\n readonly extensions: ReadonlyArray<string>;\n readonly component: string;\n readonly profiles: Readonly<Record<string, ViewerProfileApiManifest>>;\n}\n\nexport interface ViewerApiManifest {\n readonly schemaVersion: number;\n readonly viewers: Readonly<Record<string, ViewerDefinitionApiManifest>>;\n}\n\n/**\n * Platform-wide default user-facing settings. The\n * fallback floor for anonymous / pre-login rendering: timezone, date /\n * time format (CLDR tokens) and week start come from the deployment's\n * config, not an FE-hardcoded Western default. `locale` is the default\n * locale, previously absent from the manifest.\n */\nexport interface PlatformDefaults {\n readonly locale: string;\n readonly timezone: string;\n readonly dateFormat: string;\n readonly timeFormat: '12h' | '24h';\n readonly weekStart: 'monday' | 'sunday';\n /**\n * Deployment brand accent as `#rrggbb`, or null/absent to keep the\n * stylesheet's own. Sits BELOW a user's personal accentColor.\n */\n readonly accentColor?: string | null;\n}\n\nexport interface ApiManifest {\n readonly apiBase: string;\n readonly configBase?: string; // GET /api/v1/config/{type}/{id}\n readonly auth?: AuthApiManifest;\n readonly identity?: IdentityApiManifest;\n readonly sections?: SectionApiManifest;\n readonly navi?: NaviApiManifest;\n readonly content?: ContentApiManifest;\n readonly dataGrid?: DataGridApiManifest;\n readonly terminal?: TerminalApiManifest;\n readonly media?: MediaApiManifest;\n readonly document?: DocumentApiManifest;\n readonly vfs?: VfsApiManifest;\n readonly dynamicEntity?: DynamicEntityApiManifest;\n readonly domainExplorer?: DomainExplorerApiManifest;\n readonly editor?: EditorApiManifest;\n readonly viewers?: ViewerApiManifest;\n readonly supportedLocales?: Array<{ code: string; label: string }>;\n readonly platformDefaults?: PlatformDefaults;\n}\n\nexport interface ThemeConfigResponse {\n readonly slug?: string | null;\n readonly feStack?: string | null;\n readonly spaFramework?: string | null;\n readonly assetsUrl?: string | null;\n readonly manifest: ApiManifest;\n}\n\n/**\n * Replaces {param} tokens in a URL pattern with encoded values.\n *\n * @example\n * resolvePattern(manifest.sections!.item, { id: '123' })\n * // -> '/api/v1/sections/123'\n */\nexport function resolvePattern(pattern: string, params: Record<string, string>): string {\n return Object.entries(params).reduce(\n (url, [key, value]) => url.replace(`{${key}}`, encodeURIComponent(value)),\n pattern,\n );\n}\n","// Typed contract — mirrors FormRenderDefinition PHP VOs exactly.\n// No `any` anywhere.\n\nexport type FieldType =\n | 'text' | 'email' | 'password' | 'number' | 'textarea'\n | 'select' | 'toggle' | 'date' | 'time' | 'hidden'\n | 'relation' | 'subform' | 'token-pattern' | 'richtext'\n | 'localizedText' | 'localizedTextarea' | 'optionsEditor'\n // Phone module: composite international phone field — country select\n // (localized via Intl.DisplayNames + dial codes from /api/v1/phone/countries)\n // + masked number input, composing a single E.164 string value.\n | 'intlPhone'\n // Swatch picker + hex box over one value, where EMPTY is meaningful\n // (\"inherit the deployment's colour\") rather than missing. Added for the\n // per-user admin accent; nothing about it is theming-specific.\n | 'color';\n\nexport type DataSourceType = 'static' | 'enum' | 'api' | 'repo';\n\nexport type VisibilityOperator = 'eq' | 'ne' | 'in' | 'notIn' | 'empty' | 'notEmpty';\n\nexport interface VisibilityCondition {\n readonly field: string;\n readonly operator: VisibilityOperator;\n readonly value?: unknown;\n}\n\nexport type DataSourceWidget = 'select' | 'select-search' | 'select-tree' | 'token-input' | 'media-picker';\nexport type DataSourceLoading = 'eager' | 'lazy';\n\nexport interface DataSourceOption {\n readonly value: unknown;\n readonly label: string;\n readonly parentId?: string | null; // select-tree: used for client-side tree building\n}\n\nexport interface DataSourceDefinition {\n readonly type: DataSourceType;\n readonly bindValue: string;\n readonly bindLabel: string;\n readonly multiple: boolean;\n readonly maxItems?: number;\n readonly options?: ReadonlyArray<DataSourceOption>; // static + enum: resolved by backend\n readonly url?: string; // api + repo: fetch at runtime\n readonly widget: DataSourceWidget; // rendering strategy\n readonly loading: DataSourceLoading; // fetch strategy\n /** Free-form widget-specific configuration block populated from YAML\n * `dataSource.widgetOptions`. Used by media-picker for bindTarget /\n * display / accept / recentlyUsed / hoverPreview, and reserved for\n * future custom widgets. Kept separate from `options` (which is the\n * static-option list) to avoid key collisions. */\n readonly widgetOptions?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ValidatorDefinition {\n readonly type: string;\n readonly value?: unknown;\n readonly message?: string;\n}\n\nexport interface RelationDefinition {\n readonly cardinality: 'one' | 'many';\n readonly maxItems?: number;\n readonly targetFormId?: string;\n readonly dataSource?: DataSourceDefinition;\n}\n\nexport interface SubFormDefinition {\n readonly formId: string;\n readonly relation: 'one' | 'many';\n readonly maxItems?: number;\n}\n\nexport interface FieldSecurityPolicy {\n readonly read: ReadonlyArray<string>;\n readonly write: ReadonlyArray<string>;\n}\n\nexport interface FieldItem {\n readonly alias: string;\n readonly type: FieldType;\n readonly label: string;\n readonly placeholder?: string;\n readonly hint?: string;\n readonly required: boolean;\n readonly readonly: boolean;\n readonly locked: boolean;\n readonly private: boolean;\n readonly validators: ReadonlyArray<ValidatorDefinition>;\n readonly dataSource?: DataSourceDefinition;\n readonly relation?: RelationDefinition;\n readonly subForm?: SubFormDefinition;\n readonly separators?: ReadonlyArray<string>; // token-pattern field: separator chars\n readonly security?: FieldSecurityPolicy | null;\n readonly autocomplete?: string; // HTML autocomplete token (e.g. 'username', 'current-password')\n}\n\nexport interface LayoutNode {\n // A layout container, or a field alias. The `& {}` is what keeps the five\n // names meaningful: a bare `| string` absorbs them, so the union listed\n // five options the compiler had already collapsed to `string` -- no\n // completion, no checking, just documentation that looked like a type.\n readonly type: 'group' | 'grid' | 'column' | 'tabs' | 'tab' | (string & {});\n readonly name?: string;\n readonly description?: string;\n readonly compact?: boolean;\n readonly width?: number; // 1–12 grid columns\n readonly children?: ReadonlyArray<LayoutNode>;\n readonly showWhen?: VisibilityCondition;\n}\n\nexport type FormActionType = 'submit' | 'button' | 'reset';\nexport type FormActionVariant = 'primary' | 'secondary' | 'danger' | 'link';\n\n/** Terminal form button (Submit / Cancel / …) — mirrors PHP FormAction.\n * Resolved by the backend (declared `formOptions.actions`, else lifted from\n * legacy submit fields, else a default Submit). `label` is already translated. */\nexport interface FormAction {\n readonly type: FormActionType;\n readonly label: string;\n readonly name: string;\n readonly variant: FormActionVariant;\n}\n\nexport interface FormRenderDefinition {\n readonly id: string;\n readonly context: 'create' | 'edit';\n readonly layout: ReadonlyArray<LayoutNode>;\n readonly items: ReadonlyArray<FieldItem>;\n readonly actions: ReadonlyArray<FormAction>;\n readonly action?: string; // declared submit target (formOptions.action)\n}\n","import { Injectable } from '@angular/core';\nimport { State, Action, Selector } from '@ngxs/store';\nimport type { StateContext } from '@ngxs/store';\nimport { type ApiManifest, type ThemeConfigResponse, type ViewerApiManifest } from '../api/api-manifest.types';\n\n// --- Action ------------------------------------------------------------------\n\nexport class SetAppConfig {\n static readonly type = '[AppConfig] Set';\n constructor(public readonly config: ThemeConfigResponse) {}\n}\n\n// --- State -------------------------------------------------------------------\n\nexport interface AppConfigStateModel {\n config: ThemeConfigResponse | null;\n loaded: boolean;\n}\n\n@State<AppConfigStateModel>({\n name: 'appConfig',\n defaults: { config: null, loaded: false },\n})\n@Injectable()\nexport class AppConfigState {\n\n @Action(SetAppConfig)\n set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void {\n ctx.setState({ config, loaded: true });\n }\n\n @Selector()\n static manifest(state: AppConfigStateModel): ApiManifest | null {\n return state.config?.manifest ?? null;\n }\n\n /**\n * F.7 viewer manifest. Null when the backend didn't ship a viewers\n * section (no tagged providers); consumers should fall back to a\n * \"no preview\" surface.\n */\n @Selector()\n static viewers(state: AppConfigStateModel): ViewerApiManifest | null {\n return state.config?.manifest.viewers ?? null;\n }\n\n @Selector()\n static loaded(state: AppConfigStateModel): boolean {\n return state.loaded;\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { type Observable } from 'rxjs';\n\nimport { AppConfigState } from '../state/app-config.state';\nimport type { ApiManifest } from './api-manifest.types';\nimport type { TokenResponse, UserDto } from './auth.types';\n\n/**\n * The identity endpoints core owns: the session lifecycle, plus the settings\n * read at boot.\n *\n * Carved out of the admin's `ApiService` -- 2410 lines, 118 methods and 89 DTOs\n * spanning calendars, documents, calls, VFS and the rest. Core cannot depend on\n * that and still ship beneath the UI kit: it would drag every module's wire\n * types down with it. Core called exactly FOUR of those methods.\n *\n * `logout` is the one addition, and not for symmetry: the auth lifecycle lives\n * here (state, refresh coordinator, cross-tab sync), so a core package that\n * could sign in but not out would be incomplete.\n *\n * `ApiService` keeps its own identical signatures and delegates here, so none\n * of the 94 feature files calling it changed.\n */\n@Injectable({ providedIn: 'root' })\nexport class IdentityApiClient {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n\n /** URLs come from the boot manifest, which core already owns. */\n private get manifest(): ApiManifest {\n const m = this.store.selectSnapshot(AppConfigState.manifest);\n if (!m) throw new Error('ApiManifest not loaded — call AppInitService.load() first');\n return m;\n }\n\n login(identifier: string, password: string): Observable<TokenResponse> {\n return this.http.post<TokenResponse>(this.manifest.auth!.login, { identifier, password });\n }\n\n refresh(refreshToken: string): Observable<TokenResponse> {\n return this.http.post<TokenResponse>(this.manifest.auth!.refresh, { refreshToken });\n }\n\n logout(): Observable<void> {\n return this.http.post<void>(this.manifest.auth!.logout, {});\n }\n\n me(): Observable<UserDto> {\n return this.http.get<UserDto>(this.manifest.auth!.me);\n }\n\n getSettings(): Observable<Record<string, Record<string, unknown>>> {\n // Force plain JSON. Angular's default Accept carries a wildcard, which\n // makes the backend prefer application/ld+json -- and the section keys\n // are then lost inside the Hydra member array.\n return this.http.get<Record<string, Record<string, unknown>>>(\n this.manifest.identity!.settingsUrl,\n { headers: { Accept: 'application/json' } },\n );\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { type Observable } from 'rxjs';\n\nimport { AppConfigState } from '../state/app-config.state';\nimport type { ApiManifest } from './api-manifest.types';\n\n/**\n * Response of `POST /centrifugo/connection-token`: the JWT the realtime SDK\n * opens its socket with, and what it needs to refresh before expiry.\n */\nexport interface CentrifugoConnectionTokenDto {\n token: string;\n expiresAt: number;\n ttl: number;\n wsUrl: string;\n}\n\n/**\n * Response of `POST /centrifugo/subscription-token`: a per-channel JWT. The\n * SDK asks for one before subscribing to a private namespace, so this is\n * fetched per subscription rather than once per connection.\n */\nexport interface CentrifugoSubscriptionTokenDto {\n channel: string;\n token: string;\n expiresAt: number;\n ttl: number;\n}\n\n/**\n * The two realtime endpoints, carved out for the same reason the identity ones\n * were: the UI kit's realtime client needed exactly these two of `ApiService`'s\n * 117 methods, and depending on the whole thing would have pulled the entire\n * application's wire vocabulary underneath the kit.\n *\n * They belong in core rather than in the kit because both answers are derived\n * from things core already owns -- the boot manifest for the URL, and the auth\n * interceptor for the bearer token that authorises the request. A token is\n * session state, not a widget.\n *\n * `ApiService` keeps its identical signatures and delegates here, so nothing\n * calling it had to change.\n */\n@Injectable({ providedIn: 'root' })\nexport class RealtimeTokenClient {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n\n private get manifest(): ApiManifest {\n const m = this.store.selectSnapshot(AppConfigState.manifest);\n if (!m) throw new Error('ApiManifest not loaded — call AppInitService.load() first');\n return m;\n }\n\n connectionToken(): Observable<CentrifugoConnectionTokenDto> {\n return this.http.post<CentrifugoConnectionTokenDto>(\n this.manifest.apiBase + '/centrifugo/connection-token', {});\n }\n\n subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto> {\n return this.http.post<CentrifugoSubscriptionTokenDto>(\n this.manifest.apiBase + '/centrifugo/subscription-token', { channel });\n }\n}\n","import { type TokenResponse, type UserDto } from '../api/auth.types';\n\nexport class Login {\n static readonly type = '[Auth] Login';\n constructor(public identifier: string, public password: string) {}\n}\n\nexport class Logout {\n static readonly type = '[Auth] Logout';\n}\n\nexport class RestoreSession {\n static readonly type = '[Auth] Restore Session';\n}\n\nexport class SetTokens {\n static readonly type = '[Auth] Set Tokens';\n constructor(public readonly response: TokenResponse) {}\n}\n\n/** Merge a partial update into the currently-stored user (e.g. after avatar change). */\nexport class PatchCurrentUser {\n static readonly type = '[Auth] Patch Current User';\n constructor(public readonly patch: Partial<UserDto>) {}\n}\n","import { DestroyRef, inject, Injectable } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { Subject, debounceTime } from 'rxjs';\nimport { AppConfigState } from '../state/app-config.state';\nimport { type DataGridPreference } from './user-preferences.types';\n\n/**\n * Unified user preferences service — all preference types share one\n * localStorage key (`coolms_ui_prefs`) and one debounced server-sync pipeline.\n *\n * Key naming convention (stored as nested objects, referenced via dot-notation):\n *\n * // Grids\n * grids.{gridId}.columns\n * grids.{gridId}.columnWidths\n *\n * // Panels (ExplorerLayout persistKey values from YAML)\n * panel.{persistKey}.width\n * panel.{persistKey}.collapsed\n *\n * // Page navigation state\n * page.vfs.lastPath\n * page.vfs.viewMode\n * page.media.lastDir\n * page.media.viewMode\n *\n * // Global\n * nav.lastRoute\n * terminal.height\n * terminal.open\n */\n@Injectable({ providedIn: 'root' })\nexport class UserPreferencesService {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly destroyRef = inject(DestroyRef);\n\n private readonly STORAGE_KEY = 'coolms_ui_prefs';\n private readonly SYNC_DELAY = 2000; // ms\n\n private syncSubject = new Subject<Record<string, unknown>>();\n\n constructor() {\n // Debounced sync to server — batches rapid changes (e.g. live resize)\n this.syncSubject.pipe(\n debounceTime(this.SYNC_DELAY),\n takeUntilDestroyed(this.destroyRef),\n ).subscribe(prefs => this.syncToServer(prefs));\n }\n\n // -- Core helpers ---------------------------------------------------------\n\n /**\n * Write all prefs to localStorage and schedule a debounced server sync.\n */\n private save(all: Record<string, unknown>): void {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(all));\n this.syncSubject.next(all);\n }\n\n /**\n * Read the entire prefs object from localStorage, applying legacy key\n * migration on the first read after an upgrade.\n */\n private load(): Record<string, unknown> {\n try {\n const raw = localStorage.getItem(this.STORAGE_KEY);\n const all: Record<string, unknown> = raw ? JSON.parse(raw) : {};\n return this.migrate(all);\n } catch {\n return {};\n }\n }\n\n /**\n * One-time migration of legacy flat keys to the new nested naming convention.\n * Mutates `all` in place and re-persists only when something changed.\n */\n private migrate(all: Record<string, unknown>): Record<string, unknown> {\n let dirty = false;\n\n // terminal_height -> terminal.height\n if (typeof all['terminal_height'] === 'number') {\n const terminal = (all['terminal'] ?? {}) as Record<string, unknown>;\n terminal['height'] = all['terminal_height'];\n all['terminal'] = terminal;\n delete all['terminal_height'];\n dirty = true;\n }\n\n // media_view_mode -> page.media.viewMode\n if (typeof all['media_view_mode'] === 'string') {\n const page = (all['page'] ?? {}) as Record<string, unknown>;\n const media = (page['media'] ?? {}) as Record<string, unknown>;\n media['viewMode'] = all['media_view_mode'];\n page['media'] = media;\n all['page'] = page;\n delete all['media_view_mode'];\n dirty = true;\n }\n\n // media_collections_panel -> panel.media_panel_left (matches persistKey in media-library.yaml)\n if (all['media_collections_panel'] && typeof all['media_collections_panel'] === 'object') {\n const panel = (all['panel'] ?? {}) as Record<string, unknown>;\n panel['media_panel_left'] = all['media_collections_panel'];\n all['panel'] = panel;\n delete all['media_collections_panel'];\n dirty = true;\n }\n\n // Normalize namespace arrays to empty objects.\n // The server serialises empty namespaces as [] (JSON array) rather than {}\n // because PHP converts empty associative arrays to JSON arrays. Arrays silently\n // swallow named-property writes and produce \"[]\" on serialisation, so column/\n // panel preferences would be lost. Convert once and re-persist.\n const NAMESPACES = new Set(['grids', 'panel', 'page', 'terminal', 'nav']);\n for (const ns of NAMESPACES) {\n if (Array.isArray(all[ns])) {\n all[ns] = {};\n dirty = true;\n }\n }\n\n // Grid keys: any top-level object with `columns` or `columnWidths` -> grids.{key}\n for (const key of Object.keys(all)) {\n if (NAMESPACES.has(key)) continue;\n const val = all[key];\n if (val && typeof val === 'object' && !Array.isArray(val)) {\n const obj = val as Record<string, unknown>;\n if ('columns' in obj || 'columnWidths' in obj) {\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n grids[key] = obj;\n all['grids'] = grids;\n delete all[key];\n dirty = true;\n }\n }\n }\n\n if (dirty) {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(all));\n }\n\n return all;\n }\n\n // -- Server sync ----------------------------------------------------------\n\n /**\n * Seed localStorage from server preferences.\n * Called after login and on every session restore (page reload).\n *\n * Strategy:\n * - No local prefs yet (new device / clean install) -> use server prefs as-is.\n * - Local prefs already exist -> merge with local taking priority at each\n * top-level namespace. This ensures that locally-persisted state\n * (panel widths, last path, etc.) is never overwritten by the stale copy\n * that was saved in the auth token at login time.\n */\n loadFromServer(uiPrefs: Record<string, unknown>): void {\n if (Object.keys(uiPrefs).length === 0) return;\n try {\n const raw = localStorage.getItem(this.STORAGE_KEY);\n if (!raw) {\n // Fresh device — trust the server entirely\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(uiPrefs));\n } else {\n // Local prefs exist — server is the base, local namespaces win\n const local = JSON.parse(raw) as Record<string, unknown>;\n const merged = { ...uiPrefs, ...local };\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(merged));\n }\n } catch {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(uiPrefs));\n }\n }\n\n private syncToServer(prefs: Record<string, unknown>): void {\n const manifest = this.store.selectSnapshot(AppConfigState.manifest);\n const url = manifest?.auth?.preferencesUrl;\n if (!url) return;\n\n this.http.patch(url, { uiPrefs: prefs }, {\n headers: { 'Content-Type': 'application/merge-patch+json' },\n }).subscribe({\n error: err => console.warn('Preferences sync failed:', err),\n });\n }\n\n // -- Grids ----------------------------------------------------------------\n\n /**\n * Get stored preferences for a specific grid.\n */\n getGridPref(gridId: string): DataGridPreference | null {\n try {\n const all = this.load();\n const grids = all['grids'] as Record<string, unknown> | undefined;\n if (!grids) return null;\n return (grids[gridId] as DataGridPreference) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Set visible column order for a grid.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setColumns(gridId: string, columns: string[]): void {\n this.updateGrid(gridId, existing => ({ ...existing, columns }));\n }\n\n /**\n * Record a column width for a grid column.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setColumnWidth(gridId: string, field: string, width: number): void {\n this.updateGrid(gridId, existing => ({\n ...existing,\n columnWidths: {\n ...(existing.columnWidths ?? {}),\n [field]: Math.max(60, width),\n },\n }));\n }\n\n /**\n * Remove all preferences for a specific grid and immediately sync to server.\n */\n resetGrid(gridId: string): void {\n const all = this.load();\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n delete grids[gridId];\n all['grids'] = grids;\n this.save(all);\n this.syncToServer(all); // immediate sync\n }\n\n private updateGrid(\n gridId: string,\n updater: (existing: DataGridPreference) => DataGridPreference,\n ): void {\n const all = this.load();\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n grids[gridId] = updater((grids[gridId] as DataGridPreference) ?? {});\n all['grids'] = grids;\n this.save(all);\n }\n\n // -- Terminal -------------------------------------------------------------\n\n /**\n * Get persisted terminal panel height.\n */\n getTerminalHeight(): number | null {\n const all = this.load();\n const terminal = all['terminal'] as Record<string, unknown> | undefined;\n return typeof terminal?.['height'] === 'number' ? terminal['height'] : null;\n }\n\n /**\n * Persist terminal panel height.\n */\n setTerminalHeight(height: number): void {\n const all = this.load();\n const terminal = (all['terminal'] ?? {}) as Record<string, unknown>;\n terminal['height'] = Math.round(height);\n all['terminal'] = terminal;\n this.save(all);\n }\n\n // -- Panel ----------------------------------------------------------------\n\n /**\n * Get persisted state for a resizable panel identified by key.\n * Used by ExplorerLayoutComponent to persist panel widths across sessions.\n */\n getPanelState(key: string): { width: number; collapsed: boolean } | null {\n try {\n const all = this.load();\n const panel = all['panel'] as Record<string, unknown> | undefined;\n if (!panel) return null;\n const p = panel[key];\n if (!p || typeof p !== 'object') return null;\n const { width, collapsed } = p as Record<string, unknown>;\n return typeof width === 'number' && typeof collapsed === 'boolean'\n ? { width, collapsed }\n : null;\n } catch { return null; }\n }\n\n /**\n * Persist generic panel state by key.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setPanelState(key: string, width: number, collapsed: boolean): void {\n const all = this.load();\n const panel = (all['panel'] ?? {}) as Record<string, unknown>;\n panel[key] = { width: Math.round(width), collapsed };\n all['panel'] = panel;\n this.save(all);\n }\n\n // -- Page state -----------------------------------------------------------\n\n /**\n * Get all persisted state for a page namespace (e.g. 'vfs', 'media', 'nav').\n */\n getPageState<T>(pageKey: string): T | null {\n try {\n const all = this.load();\n const page = all['page'] as Record<string, unknown> | undefined;\n if (!page) return null;\n return (page[pageKey] as T) ?? null;\n } catch { return null; }\n }\n\n /**\n * Merge partial state into a page namespace.\n * Existing keys in the namespace are preserved; only provided keys are updated.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setPageState(pageKey: string, state: Record<string, unknown>): void {\n const all = this.load();\n const page = (all['page'] ?? {}) as Record<string, unknown>;\n page[pageKey] = { ...(page[pageKey] ?? {}), ...state };\n all['page'] = page;\n this.save(all);\n }\n\n // -- Media (legacy pass-through — kept for compatibility) -----------------\n\n /** @deprecated Use getPageState('media') instead */\n getMediaViewMode(): string | null {\n return this.getPageState<{ viewMode?: string }>('media')?.viewMode ?? null;\n }\n\n /** @deprecated Use setPageState('media', ...) instead */\n setMediaViewMode(mode: string): void {\n this.setPageState('media', { viewMode: mode });\n }\n\n /** @deprecated Use getPanelState('media_panel_left') instead */\n getMediaCollectionsPanel(): { width: number; collapsed: boolean } | null {\n return this.getPanelState('media_panel_left');\n }\n\n /** @deprecated Use setPanelState('media_panel_left', ...) instead */\n setMediaCollectionsPanel(width: number, collapsed: boolean): void {\n this.setPanelState('media_panel_left', width, collapsed);\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { State, Action, Selector } from '@ngxs/store';\nimport type { StateContext } from '@ngxs/store';\nimport { catchError, of } from 'rxjs';\nimport { switchMap, tap } from 'rxjs/operators';\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport type { TokenResponse, UserDto } from '../api/auth.types';\nimport { Login, Logout, PatchCurrentUser, RestoreSession, SetTokens } from './auth.actions';\nimport { UserPreferencesService } from '../services/user-preferences.service';\n\nexport interface AuthStateModel {\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: string | null;\n user: UserDto | null;\n}\n\nconst STORAGE_KEY = 'coolms_token';\n\n@State<AuthStateModel>({\n name: 'auth',\n defaults: { accessToken: null, refreshToken: null, expiresAt: null, user: null },\n})\n@Injectable()\nexport class AuthState {\n // `inject()` rather than constructor parameters, and the imports above are\n // VALUE imports on purpose: an injected type must survive to runtime. As a\n // constructor parameter that is invisible to `tsc`, which type-checks a\n // type-only import happily and leaves the Angular compiler to fail with\n // NG2003. Read as a value here, so the same mistake is a type error.\n private readonly api = inject(IdentityApiClient);\n private readonly prefs = inject(UserPreferencesService);\n\n @Action(Login)\n login(ctx: StateContext<AuthStateModel>, { identifier, password }: Login) {\n return this.api.login(identifier, password).pipe(\n tap((res: TokenResponse) => {\n const state: AuthStateModel = {\n accessToken: res.accessToken,\n refreshToken: res.refreshToken,\n expiresAt: res.expiresAt,\n user: res.user ?? null,\n };\n ctx.setState(state);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n }),\n // Fetch full user data (includes uiPrefs) to seed preferences\n switchMap(() => this.api.me().pipe(catchError(() => of(null)))),\n tap(me => {\n if (!me) return;\n // Update stored user with full data including uiPrefs\n const updated = { ...ctx.getState(), user: me };\n ctx.setState(updated);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));\n if (me.uiPrefs) {\n this.prefs.loadFromServer(me.uiPrefs);\n }\n }),\n );\n }\n\n @Action(Logout)\n logout(ctx: StateContext<AuthStateModel>) {\n ctx.setState({ accessToken: null, refreshToken: null, expiresAt: null, user: null });\n localStorage.removeItem(STORAGE_KEY);\n }\n\n @Action(RestoreSession)\n restore(ctx: StateContext<AuthStateModel>) {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n\n try {\n const state = JSON.parse(raw) as AuthStateModel;\n // Restore whenever any token is present.\n // If only the access token has expired but the refresh token is still\n // valid, the HTTP interceptor will transparently refresh on the next\n // request. Discarding state here would throw away the refresh token\n // and force a full re-login unnecessarily.\n if (state.accessToken || state.refreshToken) {\n ctx.setState(state);\n // Seed preferences from previously stored user (covers page-reload case)\n if (state.user?.uiPrefs) {\n this.prefs.loadFromServer(state.user.uiPrefs);\n }\n } else {\n localStorage.removeItem(STORAGE_KEY);\n }\n } catch {\n localStorage.removeItem(STORAGE_KEY);\n }\n }\n\n @Action(SetTokens)\n setTokens(ctx: StateContext<AuthStateModel>, { response }: SetTokens): void {\n const state: AuthStateModel = {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n expiresAt: response.expiresAt,\n user: response.user ?? ctx.getState().user,\n };\n ctx.setState(state);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n }\n\n @Action(PatchCurrentUser)\n patchCurrentUser(ctx: StateContext<AuthStateModel>, { patch }: PatchCurrentUser): void {\n const current = ctx.getState();\n if (!current.user) return;\n const updated = { ...current, user: { ...current.user, ...patch } };\n ctx.setState(updated);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));\n }\n\n @Selector()\n static isAuthenticated(state: AuthStateModel): boolean {\n return null !== state.accessToken;\n }\n\n @Selector()\n static accessToken(state: AuthStateModel): string | null {\n return state.accessToken;\n }\n\n @Selector()\n static refreshToken(state: AuthStateModel): string | null {\n return state.refreshToken;\n }\n\n /**\n * Sub-phase Y -- expose the access-token expiry timestamp so\n * preemptive-refresh callers (e.g., Centrifuge getToken\n * callbacks) can decide whether to rotate before issuing the\n * next protected request, instead of relying on the\n * interceptor's reactive 401-retry path.\n */\n @Selector()\n static expiresAt(state: AuthStateModel): string | null {\n return state.expiresAt;\n }\n\n @Selector()\n static currentUser(state: AuthStateModel): UserDto | null {\n return state.user;\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { type Observable, catchError, defer, finalize, firstValueFrom, from, shareReplay, throwError } from 'rxjs';\n\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport { AuthState } from './auth.state';\nimport { Logout, SetTokens } from './auth.actions';\n\nconst LOCK_NAME = 'coolms_auth_refresh';\nconst FRESH_TOKEN_GRACE_MS = 30_000;\n\n/**\n * Two-layer refresh coordinator: an intra-tab `shareReplay(1)`\n * mutex dedupes concurrent callers within one tab, and a\n * cross-tab `navigator.locks` wrapper ensures only one tab in\n * the browser issues the actual `POST /auth/refresh` at a time.\n *\n * Without the cross-tab layer, two tabs racing the same expired\n * refresh-token both POST `/auth/refresh`; the backend's\n * `isUsed` replay branch revokes every token for the user on\n * the second arrival and the operator is kicked to /login.\n *\n * The Web Lock body re-reads the current access-token expiry from\n * `AuthState` before issuing the HTTP call. When this tab waited\n * behind another tab's refresh, that tab's `SetTokens` dispatch\n * has already published fresh values into shared `localStorage`,\n * `CrossTabAuthSyncService` has mirrored them into this tab's\n * NGXS state, and the lock body simply returns the cached access\n * token without another network round-trip.\n *\n * `Logout` fires only when the refresh endpoint returns 401\n * (real auth failure). Transient errors (network blips, 5xx,\n * CORS, timeouts) propagate without clearing the session so a\n * momentary refresh failure does not kill a valid login.\n */\n@Injectable({ providedIn: 'root' })\nexport class AuthRefreshCoordinator {\n private readonly api = inject(IdentityApiClient);\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n\n private inFlight: Observable<string> | null = null;\n\n /**\n * Return the in-flight refresh Observable, or spawn a new one\n * with the supplied refresh-token value.\n *\n * Callers subscribe via `switchMap` / `firstValueFrom` to retry\n * their original request with the resolved access token.\n */\n refresh(refreshToken: string): Observable<string> {\n if (this.inFlight) {\n return this.inFlight;\n }\n this.inFlight = defer(() => this.refreshWithCrossTabLock(refreshToken)).pipe(\n catchError(refreshErr => {\n const isAuthFailure = refreshErr instanceof HttpErrorResponse && refreshErr.status === 401;\n if (isAuthFailure) {\n this.store.dispatch(new Logout());\n if (this.router.navigated) {\n void this.router.navigate(['/login']);\n }\n }\n return throwError(() => refreshErr);\n }),\n shareReplay(1),\n finalize(() => {\n this.inFlight = null;\n }),\n );\n return this.inFlight;\n }\n\n /**\n * Run the refresh body inside `navigator.locks` when the API is\n * available; degrade to direct invocation otherwise. The lock\n * body checks for state already updated by a sibling tab before\n * issuing its own HTTP call.\n */\n private refreshWithCrossTabLock(refreshToken: string): Observable<string> {\n const lockBody = (): Promise<string> => this.executeRefresh(refreshToken);\n\n if (typeof navigator === 'undefined' || !('locks' in navigator)) {\n return from(lockBody());\n }\n return from(\n navigator.locks.request(LOCK_NAME, lockBody).then(value => {\n if (value === undefined) {\n throw new Error('Refresh lock aborted before completing.');\n }\n return value;\n }),\n );\n }\n\n /**\n * Lock body. Re-reads `AuthState.expiresAt` first: if another\n * tab published fresh tokens while we were queued, return the\n * cached access token directly. Otherwise read the latest\n * refresh-token value from state (it may have rotated while we\n * waited) and issue the HTTP refresh.\n */\n private async executeRefresh(originalRefreshToken: string): Promise<string> {\n const cachedAccess = this.tryConsumeFreshState();\n if (cachedAccess !== null) {\n return cachedAccess;\n }\n const currentRefreshToken = this.store.selectSnapshot(AuthState.refreshToken) ?? originalRefreshToken;\n const response = await firstValueFrom(this.api.refresh(currentRefreshToken));\n this.store.dispatch(new SetTokens(response));\n return response.accessToken;\n }\n\n /**\n * Return the cached access token when `AuthState.expiresAt` has\n * more than `FRESH_TOKEN_GRACE_MS` milliseconds remaining --\n * signalling that some other tab refreshed for us. Null\n * otherwise.\n */\n private tryConsumeFreshState(): string | null {\n const accessToken = this.store.selectSnapshot(AuthState.accessToken);\n const expiresAtStr = this.store.selectSnapshot(AuthState.expiresAt);\n if (accessToken === null || expiresAtStr === null) {\n return null;\n }\n const expiresAtMs = new Date(expiresAtStr).getTime();\n if (Number.isNaN(expiresAtMs)) {\n return null;\n }\n if (Date.now() < expiresAtMs - FRESH_TOKEN_GRACE_MS) {\n return accessToken;\n }\n return null;\n }\n}\n","import { inject } from '@angular/core';\nimport {\n HttpContextToken, type HttpInterceptorFn,\n HttpErrorResponse,\n} from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { Router } from '@angular/router';\nimport { AuthState } from './auth.state';\nimport { Logout } from './auth.actions';\nimport { AuthRefreshCoordinator } from './auth-refresh.coordinator';\nimport { AppInitService } from '../bootstrap/app-init.service';\nimport { catchError, switchMap, take, throwError } from 'rxjs';\n\n// -- Opt-out context token -----------------------------------------------------\n//\n// Set BYPASS_AUTH to true on any HttpRequest to skip adding the Authorization\n// header entirely. Used by AppInitService for the bootstrap config request so\n// that a stale localStorage token does not cause a 401 on a public endpoint.\n\nexport const BYPASS_AUTH = new HttpContextToken<boolean>(() => false);\n\n// -----------------------------------------------------------------------------\n\nexport const authInterceptor: HttpInterceptorFn = (req, next) => {\n const store = inject(Store);\n const router = inject(Router);\n const coordinator = inject(AuthRefreshCoordinator);\n const initService = inject(AppInitService);\n\n // Caller explicitly requested no auth header (e.g. the bootstrap config\n // fetch — the endpoint is public and a stale token would cause 401).\n if (req.context.get(BYPASS_AUTH)) {\n return next(req);\n }\n\n // Auth endpoints are public by design. The backend's JWT firewall rejects any\n // request that carries an invalid/expired Bearer token — even on endpoints\n // that would otherwise allow anonymous access — so never forward a stored\n // token to login or refresh.\n if (req.url.includes('/auth/login') || req.url.includes('/auth/refresh')) {\n return next(req);\n }\n\n // Queue behind AppInitService.ready$ so that token restoration from\n // localStorage (RestoreSession) is guaranteed to be in NGXS state before\n // we read it. On F5 the interceptor fires synchronously for every pending\n // request before APP_INITIALIZER has completed; without this guard the\n // token snapshot is null and every request gets a 401 -> refresh cycle even\n // for a perfectly valid session.\n //\n // ready$ is a ReplaySubject(1) that completes after load() finishes, so:\n // - requests fired during init queue here and flush the moment ready$ emits;\n // - requests fired after init pass through take(1) immediately (replayed value).\n return initService.ready$.pipe(\n take(1),\n switchMap(() => {\n const token = store.selectSnapshot(AuthState.accessToken);\n const authed = token\n ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })\n : req;\n\n return next(authed).pipe(\n catchError((err: unknown) => {\n if (!(err instanceof HttpErrorResponse) || err.status !== 401) {\n return throwError(() => err);\n }\n\n // Avoid infinite loop — skip refresh for auth endpoints themselves.\n if (req.url.includes('/auth/refresh') || req.url.includes('/auth/login')) {\n return throwError(() => err);\n }\n\n const refreshToken = store.selectSnapshot(AuthState.refreshToken);\n if (!refreshToken) {\n store.dispatch(new Logout());\n // Only navigate programmatically once the router has already\n // completed its initial navigation. During APP_INITIALIZER the\n // router is still blocked; authGuard handles the redirect there.\n if (router.navigated) void router.navigate(['/login']);\n return throwError(() => err);\n }\n\n // Delegate refresh to the shared coordinator so the\n // Centrifuge getToken path and the interceptor share ONE\n // in-flight refresh. Without this, two parallel\n // `api.refresh(refreshToken)` calls (one from each mutex)\n // would send the same refresh-token twice and trigger the\n // backend's `isUsed` replay branch on the second arrival,\n // wiping the whole session.\n return coordinator.refresh(refreshToken).pipe(\n switchMap(newToken =>\n next(req.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } })),\n ),\n );\n }),\n );\n }),\n );\n};\n","import { Injectable, inject } from '@angular/core';\nimport { Store } from '@ngxs/store';\n\nimport type { AuthStateModel } from './auth.state';\nimport { Logout, SetTokens } from './auth.actions';\nimport type { TokenResponse } from '../api/auth.types';\n\nconst STORAGE_KEY = 'coolms_token';\n\n/**\n * Bridges shared `localStorage` mutations into the NGXS auth\n * state so a refresh executed in one tab silently propagates to\n * every other open tab.\n *\n * The browser fires `storage` events in tabs OTHER than the one\n * that mutated `localStorage`. So when Tab A's\n * `AuthRefreshCoordinator` rotates the token pair and `SetTokens`\n * persists the new values, every sibling tab receives an event\n * here and dispatches `SetTokens` against its own NGXS state --\n * no extra HTTP roundtrip, no extra refresh contention.\n *\n * Pairs with the `navigator.locks` cross-tab mutex in\n * `AuthRefreshCoordinator`: locks ensure only one tab issues the\n * refresh, this listener propagates the result to the rest.\n *\n * `Logout` from any tab clears `coolms_token` (newValue === null)\n * and this listener mirrors that into the sibling tabs so the\n * shell redirects to `/login` without waiting for the next\n * protected request to 401.\n *\n * Started from `AppInitService.load()` so the listener is in\n * place before any protected HTTP fires.\n */\n@Injectable({ providedIn: 'root' })\nexport class CrossTabAuthSyncService {\n private readonly store = inject(Store);\n private listenerAttached = false;\n\n start(): void {\n if (this.listenerAttached) {\n return;\n }\n this.listenerAttached = true;\n window.addEventListener('storage', this.handleStorageEvent);\n }\n\n private readonly handleStorageEvent = (event: StorageEvent): void => {\n if (event.key !== STORAGE_KEY) {\n return;\n }\n // Sibling tab cleared the key (logout dispatch or\n // RestoreSession parse-error cleanup). Mirror the clear so\n // this tab does not keep operating against a revoked\n // server-side session.\n if (event.newValue === null) {\n this.store.dispatch(new Logout());\n return;\n }\n // Parse the new value and dispatch SetTokens so this tab's\n // in-memory NGXS state reflects what the originating tab\n // just wrote. Malformed payloads are ignored.\n let parsed: AuthStateModel;\n try {\n parsed = JSON.parse(event.newValue) as AuthStateModel;\n } catch {\n return;\n }\n if (typeof parsed.accessToken !== 'string' || typeof parsed.refreshToken !== 'string' || typeof parsed.expiresAt !== 'string') {\n return;\n }\n const tokenResponse: TokenResponse = {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: parsed.expiresAt,\n user: parsed.user ?? undefined,\n };\n this.store.dispatch(new SetTokens(tokenResponse));\n };\n}\n","import { Injectable, inject } from '@angular/core';\nimport { Location } from '@angular/common';\nimport { HttpClient, HttpContext } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { catchError, firstValueFrom, map, of, ReplaySubject, tap } from 'rxjs';\nimport { Logout, RestoreSession } from '../auth/auth.actions';\nimport { AuthState } from '../auth/auth.state';\nimport { BYPASS_AUTH } from '../auth/auth.interceptor';\nimport { AuthRefreshCoordinator } from '../auth/auth-refresh.coordinator';\nimport { CrossTabAuthSyncService } from '../auth/cross-tab-auth-sync.service';\nimport { SetAppConfig } from '../state/app-config.state';\nimport { type ThemeConfigResponse } from '../api/api-manifest.types';\nimport { UserPreferencesService } from '../services/user-preferences.service';\n\n@Injectable({ providedIn: 'root' })\nexport class AppInitService {\n // Injected as values, not constructor parameters: an injection token has to\n // exist at runtime, and a type-only import of one type-checks clean while\n // the Angular compiler rejects it with NG2003.\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n private readonly location = inject(Location);\n private readonly prefs = inject(UserPreferencesService);\n private readonly crossTabSync = inject(CrossTabAuthSyncService);\n private readonly refreshCoordinator = inject(AuthRefreshCoordinator);\n\n /** The only hardcoded URL — everything else comes from the manifest. */\n private readonly configUrl = '/api/v1/theme/config';\n\n /**\n * Emits (once) when load() has fully completed — tokens settled, manifest\n * set (or swallowed on error). authGuard subscribes to this so it never\n * evaluates isAuthenticated against a partially-restored stale token.\n */\n private readonly initComplete = new ReplaySubject<void>(1);\n readonly ready$ = this.initComplete.asObservable();\n\n async load(): Promise<void> {\n // -- Step 0: cross-tab sync --------------------------------------------\n //\n // Install the storage-event listener before RestoreSession so that\n // any localStorage mutation from a sibling tab during bootstrap\n // (e.g., a parallel tab refreshing tokens just as we load) is mirrored\n // into this tab's NGXS state.\n this.crossTabSync.start();\n\n // -- Step 1: restore tokens from localStorage --------------------------\n //\n // RestoreSession is synchronous (ctx.setState from localStorage), so\n // this await resolves in the same microtask. Tokens are in NGXS state\n // before ANY subsequent HTTP request fires — the auth interceptor reads\n // the token from state when attaching Authorization headers, so this\n // ordering prevents unauthenticated requests on page reload (F5).\n await firstValueFrom(this.store.dispatch(new RestoreSession()));\n\n // -- Step 2: fetch the manifest ----------------------------------------\n //\n // BYPASS_AUTH is required here — not optional. The manifest endpoint\n // is public, but two specific failure modes make bypassing necessary:\n //\n // a) The stored access token may be expired. Without BYPASS_AUTH the\n // interceptor attaches the expired token, gets a 401, and tries to\n // refresh. But the refresh endpoint URL comes from the manifest\n // (api.refresh() reads manifest.auth.refresh), which hasn't been\n // loaded yet — triggering a null-dereference crash before the app\n // can recover.\n //\n // b) Even if the token is valid, attaching it to a public endpoint\n // that ignores auth adds unnecessary coupling.\n //\n // Consequence: every page load issues one unauthenticated config\n // request, which is intentional. All subsequent API calls carry the\n // restored (or refreshed) token normally.\n const config = await firstValueFrom(\n this.http.get<ThemeConfigResponse>(this.configUrl, {\n context: new HttpContext().set(BYPASS_AUTH, true),\n }).pipe(\n tap(cfg => this.store.dispatch(new SetAppConfig(cfg))),\n catchError(() => {\n console.warn('CoolMS: theme/config unreachable, using safe defaults');\n return of(null);\n }),\n ),\n );\n\n // -- Step 3: validate the restored session -----------------------------\n //\n // RestoreSession trusts whatever tokens are in localStorage, but they may\n // have been revoked out-of-band — most notably, signing out on the public\n // SSR site revokes the user's WHOLE token set. We MUST settle the real\n // auth state here, BEFORE step 4 signals ready$: both the auth interceptor\n // AND authGuard gate on ready$, so if we signalled first, the guard would\n // race ahead and mount the shell against the stale isAuthenticated=true\n // before this probe finished (-> half-loaded shell + \"Session expired\"\n // toasts, the exact bug this fixes). Because the probe itself cannot wait\n // on ready$ (that would deadlock), validateSession() bypasses the\n // interceptor and attaches the token / drives the refresh by hand.\n const meUrl = config?.manifest.identity?.meUrl;\n if (meUrl && this.store.selectSnapshot(AuthState.isAuthenticated)) {\n await this.validateSession(meUrl);\n }\n\n // -- Step 4: signal authGuard + unblock the interceptor ----------------\n //\n // Auth state is now settled (session confirmed, refreshed, or Logout\n // dispatched). ReplaySubject(1) replays to guards/requests that subscribe\n // afterwards, so they evaluate against the final isAuthenticated value.\n this.initComplete.next();\n this.initComplete.complete();\n\n // -- Step 5: navigate to last-visited route ----------------------------\n //\n // Only when the session survived validation. If it did not, isAuthenticated\n // is now false, we skip the saved-route navigation, and authGuard sends the\n // initial navigation to /login.\n //\n // Only restore when the user opened the bare /admin root. An explicit\n // deep link (pasted /admin/sync-fleet, a bookmark, a shared URL) must\n // win over the saved route. The router cannot tell us which one this is:\n // withEnabledBlockingInitialNavigation holds the initial navigation until\n // this initializer resolves, so router.url is still '/'. The browser\n // location is the only record of what was requested — Location.path()\n // reads it with the /admin base href stripped, so the bare root (with or\n // without trailing slash) yields ''. Anything else, including a root URL\n // carrying query params, is treated as an explicit destination.\n if (this.store.selectSnapshot(AuthState.isAuthenticated)) {\n const requested = this.location.path();\n if (requested === '' || requested === '/') {\n const saved = this.prefs.getPageState<{ lastRoute?: string }>('nav');\n if (saved?.lastRoute && !saved.lastRoute.startsWith('/login')) {\n void this.router.navigateByUrl(saved.lastRoute);\n }\n }\n }\n }\n\n /**\n * Confirm the restored session is still alive, settling AuthState before the\n * app unblocks. Runs OUTSIDE the auth interceptor (BYPASS_AUTH) because the\n * interceptor queues behind ready$, which has not fired yet at this point.\n *\n * - access token accepted -> done, session alive;\n * - access token rejected -> attempt ONE refresh (access tokens are short-lived,\n * so an expired one with a valid refresh token must\n * stay logged in). The coordinator dispatches Logout\n * on a refresh 401 and SetTokens on success; a\n * transient failure (5xx/network) is swallowed so a\n * momentary blip never kills a valid session.\n */\n private async validateSession(meUrl: string): Promise<void> {\n const token = this.store.selectSnapshot(AuthState.accessToken);\n if (await this.probe(meUrl, token)) {\n return;\n }\n const refreshToken = this.store.selectSnapshot(AuthState.refreshToken);\n if (!refreshToken) {\n this.store.dispatch(new Logout());\n return;\n }\n let refreshedToken: string | null = null;\n try {\n refreshedToken = await firstValueFrom(this.refreshCoordinator.refresh(refreshToken));\n } catch {\n // 401 -> coordinator already dispatched Logout; transient -> session kept.\n return;\n }\n // Refresh resolved — but the coordinator's fresh-state optimisation can hand\n // back the SAME access token (without a network round-trip) when its stored\n // expiry still looks fresh. For a token that was revoked but not yet expired\n // (signing out on the SSR site while the access token is still in its TTL),\n // that token is dead despite looking fresh. Re-probe with whatever we now\n // hold; a second rejection means the session is genuinely gone.\n if (!(await this.probe(meUrl, refreshedToken))) {\n this.store.dispatch(new Logout());\n }\n }\n\n /** GET an authed endpoint with an explicit Bearer header, bypassing the interceptor. */\n private probe(url: string, token: string | null): Promise<boolean> {\n return firstValueFrom(\n this.http.get(url, {\n context: new HttpContext().set(BYPASS_AUTH, true),\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n }).pipe(\n map(() => true),\n catchError(() => of(false)),\n ),\n );\n }\n}\n","import { inject } from '@angular/core';\nimport { type CanActivateFn, Router } from '@angular/router';\nimport { map, take } from 'rxjs';\nimport { Store } from '@ngxs/store';\nimport { AuthState } from './auth.state';\nimport { AppInitService } from '../bootstrap/app-init.service';\n\n/**\n * Protects routes that require authentication.\n *\n * The guard is intentionally ASYNC: it waits for AppInitService.ready$ before\n * evaluating auth state. Without this, Angular's router can evaluate\n * isAuthenticated while APP_INITIALIZER is still in-flight — RestoreSession\n * will have placed a non-null (but expired) token in state, causing the guard\n * to pass even though the subsequent config/refresh requests will fail and\n * dispatch Logout. Waiting for ready$ guarantees the auth state is settled.\n */\nexport const authGuard: CanActivateFn = () => {\n const init = inject(AppInitService);\n const store = inject(Store);\n const router = inject(Router);\n\n return init.ready$.pipe(\n take(1),\n map(() => {\n if (store.selectSnapshot(AuthState.isAuthenticated)) {\n return true;\n }\n return router.createUrlTree(['/login']);\n }),\n );\n};\n","import { inject } from '@angular/core';\nimport { type CanActivateFn, Router } from '@angular/router';\nimport { map, take } from 'rxjs';\nimport { Store } from '@ngxs/store';\nimport { AuthState } from './auth.state';\nimport { AppInitService } from '../bootstrap/app-init.service';\n\n/**\n * Guards the `/login` route -- redirects already-authenticated\n * callers to admin home so a stale tab parked on `/login` cannot\n * remain mounted while another tab is logged in. When Tab A logs\n * in fresh, Tab B's `RestoreSession` (driven by APP_INITIALIZER)\n * reads the new token from shared `localStorage` and populates\n * `AuthState`; this guard then sees `isAuthenticated === true`\n * and routes Tab B to `/`.\n *\n * Same `ready$` wait as `authGuard` -- ensures `RestoreSession`\n * has run before the snapshot is read; otherwise a fresh F5 on\n * `/login` with a valid stored session would briefly evaluate\n * as anonymous, allow `/login`, and only then have the token\n * populated.\n */\nexport const loginPageGuard: CanActivateFn = () => {\n const init = inject(AppInitService);\n const store = inject(Store);\n const router = inject(Router);\n\n return init.ready$.pipe(\n take(1),\n map(() => {\n if (store.selectSnapshot(AuthState.isAuthenticated)) {\n return router.createUrlTree(['/']);\n }\n return true;\n }),\n );\n};\n","import { Component, inject } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NgIf } from '@angular/common';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { Login } from '../auth.actions';\n\n@Component({\n selector: 'coolms-admin-login',\n standalone: true,\n imports: [FormsModule, NgIf],\n templateUrl: './login.component.html',\n})\nexport class LoginComponent {\n identifier = '';\n password = '';\n loading = false;\n error: string | null = null;\n\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n\n submit(): void {\n if (!this.identifier || !this.password) return;\n\n this.loading = true;\n this.error = null;\n\n this.store.dispatch(new Login(this.identifier, this.password)).subscribe({\n next: () => {\n // A rejected navigation used to vanish: nothing resets `loading`\n // on the success path, because the page is expected to go\n // away. If it does not, the form spins for ever with no reason\n // given, so say so instead.\n void this.router.navigate(['/']).catch(() => {\n this.loading = false;\n this.error = 'Signed in, but the admin could not be opened. Please reload.';\n });\n },\n error: (err) => {\n this.loading = false;\n this.error = err?.error?.detail ?? err?.error?.message ?? err?.message ?? 'Invalid credentials. Please try again.';\n },\n });\n }\n}\n","<div class=\"d-flex align-items-center justify-content-center min-vh-100 bg-light\">\n <div class=\"card shadow-sm\" style=\"width: 360px\">\n <div class=\"card-body p-4\">\n <h1 class=\"h5 mb-1\">CoolMS Admin</h1>\n <p class=\"text-muted small mb-4\">Sign in to continue</p>\n\n <div *ngIf=\"error\" class=\"alert alert-danger py-2 small\">{{ error }}</div>\n\n <form (ngSubmit)=\"submit()\" #loginForm=\"ngForm\">\n <div class=\"mb-3\">\n <label class=\"form-label\">Email or username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n [(ngModel)]=\"identifier\"\n name=\"identifier\"\n required\n autocomplete=\"username\"\n />\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n [(ngModel)]=\"password\"\n name=\"password\"\n required\n autocomplete=\"current-password\"\n />\n </div>\n <button\n type=\"submit\"\n class=\"cms-btn cms-btn-primary w-100 justify-content-center\"\n [disabled]=\"loading || loginForm.invalid\"\n >\n {{ loading ? 'Signing in…' : 'Sign in' }}\n </button>\n </form>\n </div>\n </div>\n</div>\n","import { inject, Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { map, type Observable, shareReplay } from 'rxjs';\nimport { AppConfigState } from '../state/app-config.state';\n\n/**\n * Layout config DTO -- the shape served from\n * `GET /api/v1/config/layout/{id}`. Mirrors the YAML at\n * `config/modules/{module}/layout/*.yaml`.\n *\n * The optional fields below are the page-level contract\n * every admin SPA page resolves through. The base contract (id /\n * template / title / icon / slots) is the legacy list shape and stays\n * untouched. Existing 17 list YAMLs render identically when the new\n * fields are absent.\n *\n * New optional fields:\n * - `subtitle` one-line page description rendered under the title\n * - `sections` ordered map of body sections, each with its own slot graph\n * (replaces single-slot `slots:` for multi-section pages)\n * - `headerActions` buttons displayed in the cms-page-header action bar\n * - `permissions` hint for clients about who this page targets; real gating\n * lives on the data APIs the slot components consume\n * (the layout endpoint itself is public by design --\n * layouts carry only structure, never data)\n *\n * `[key: string]: unknown` index signature stays so existing per-page\n * keys (e.g. `media:library`'s `naviGraph:` slot shorthand) keep working\n * without further interface changes.\n */\nexport interface LayoutHeaderAction {\n id: string;\n label?: string;\n icon?: string;\n title?: string;\n primary?: boolean;\n danger?: boolean;\n disabled?: boolean;\n /**\n * Optional capability gate evaluated FE-side against runtime state\n * (e.g. `administer` / `delete`) — the config declares WHICH actions\n * exist + their gate; the FE decides if THIS user/row qualifies. Absent\n * = always shown. Used by detail pages whose actions are config-driven\n * (see `web:section-detail`).\n *\n * For a gate that depends on the RECORD rather than the user, prefer\n * `showWhen` below: `requires` needs a page to interpret the token, so\n * every new value costs a branch in TypeScript.\n */\n requires?: string;\n /**\n * Conditions on the page's published context, same grammar and same\n * evaluator as a NaviGraph toolbar node, applied\n * by {@link LayoutActionsService}:\n *\n * showWhen — the action does not APPLY, so it is absent.\n * disabledWhen — temporarily UNAVAILABLE: stays put and greys out.\n * busyWhen — the action is RUNNING: greys out too, and may relabel.\n * busyLabel — label shown while `busyWhen` holds (\"Saving…\").\n * activeWhen — pressed/active styling.\n *\n * Two carriers (a layout config and a NaviGraph tree) but ONE vocabulary:\n * a page that moves between them should not have to relearn how to say\n * \"not now\".\n */\n showWhen?: Record<string, unknown>;\n disabledWhen?: Record<string, unknown>;\n busyWhen?: Record<string, unknown>;\n busyLabel?: string;\n activeWhen?: Record<string, unknown>;\n}\n\nexport interface LayoutSection {\n title?: string;\n /**\n * When true, the layout shell pins the section to the top of the\n * scroll container (CSS `position: sticky`). Used by inspector /\n * filter pages where an input form should stay visible while the\n * operator scrolls through results below. Currently honoured by\n * cms-inspector-layout; cms-detail-layout will follow when it\n * lands. Default false.\n */\n sticky?: boolean;\n slots?: Record<string, { component?: string; [key: string]: unknown }>;\n [key: string]: unknown;\n}\n\nexport interface LayoutConfig {\n id?: string;\n template?: string;\n title?: string;\n subtitle?: string;\n icon?: string;\n slots?: Record<string, unknown>;\n sections?: Record<string, LayoutSection>;\n headerActions?: LayoutHeaderAction[];\n /**\n * Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)\n * — primary / destructive actions kept out of the cramped header.\n * Same shape as `headerActions`; gated per-item via `requires`.\n */\n footerActions?: LayoutHeaderAction[];\n permissions?: string[];\n [key: string]: unknown;\n}\nexport interface DialogConfig { [key: string]: unknown; }\nexport interface NavigraphConfig { [key: string]: unknown; }\nexport interface FormConfig { [key: string]: unknown; }\nexport interface DatagridConfig { [key: string]: unknown; }\n\n/**\n * Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.\n *\n * Responses are cached for the lifetime of the service (Map + shareReplay(1))\n * so repeated calls never re-fetch the same resource.\n *\n * configBase is read from the API manifest (manifest.configBase) and falls\n * back to '/api/v1/config' when the manifest is not yet loaded.\n */\n@Injectable({ providedIn: 'root' })\nexport class ConfigService {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly cache = new Map<string, Observable<unknown>>();\n\n layout(id: string): Observable<LayoutConfig> { return this.fetch<LayoutConfig>('layout', id); }\n dialog(id: string): Observable<DialogConfig> { return this.fetch<DialogConfig>('dialog', id); }\n navigraph(slug: string): Observable<NavigraphConfig> { return this.fetch<NavigraphConfig>('navigraph', slug); }\n form(id: string): Observable<FormConfig> { return this.fetch<FormConfig>('form', id); }\n datagrid(id: string): Observable<DatagridConfig> { return this.fetch<DatagridConfig>('datagrid', id); }\n\n private fetch<T>(type: string, id: string): Observable<T> {\n const key = `${type}/${id}`;\n\n if (!this.cache.has(key)) {\n const base = this.store.selectSnapshot(AppConfigState.manifest)?.configBase\n ?? '/api/v1/config';\n // The config endpoint wraps the YAML payload in a `data` field:\n // { type, id, data: { ...yaml content... } }\n // Unwrap so callers always receive the config payload directly.\n const obs = this.http.get<{ data?: T } & T>(`${base}/${type}/${id}`).pipe(\n map(r => (r.data ?? r) as T),\n shareReplay(1),\n );\n this.cache.set(key, obs);\n }\n\n return this.cache.get(key) as Observable<T>;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\n\n/**\n * Converts any caught error into a human-readable message.\n *\n * Priority order for backend errors:\n * 1. error.detail (RFC 7807 problem detail)\n * 2. error['hydra:description'] (Hydra validation errors)\n * 3. error.message (generic)\n * 4. error.violations[] (constraint violations)\n * 5. Status-based fallback\n */\n@Injectable({ providedIn: 'root' })\nexport class ErrorHandlerService {\n humanize(err: unknown): string {\n if (!(err instanceof HttpErrorResponse)) {\n return err instanceof Error ? err.message : 'An unexpected error occurred.';\n }\n\n const body = err.error as Record<string, unknown> | null;\n if (body && typeof body === 'object') {\n const detail = body['detail'] ?? body['hydra:description'] ?? body['message'];\n if (typeof detail === 'string' && detail.trim()) {\n return detail.trim();\n }\n\n if (Array.isArray(body['violations'])) {\n const msgs = (body['violations'] as Array<{ message: string }>)\n .map(v => v.message)\n .filter(Boolean);\n if (msgs.length) return msgs.join(' ');\n }\n }\n\n return this.statusMessage(err.status);\n }\n\n private statusMessage(status: number): string {\n switch (status) {\n case 0: return 'Network error. Check your connection.';\n case 400: return 'Invalid request.';\n case 401: return 'Session expired. Please sign in again.';\n case 403: return 'Access denied.';\n case 404: return 'Resource not found.';\n case 409: return 'Conflict — resource already exists.';\n case 422: return 'Validation failed.';\n case 429: return 'Too many requests. Please wait a moment.';\n case 500: return 'Server error. Please try again later.';\n case 503: return 'Service unavailable. Please try again later.';\n default: return `Unexpected error (${status}).`;\n }\n }\n}\n","import { computed, effect, inject, Injectable, signal } from '@angular/core';\nimport { type Observable, of } from 'rxjs';\nimport { catchError, map, shareReplay, tap } from 'rxjs/operators';\nimport { Store } from '@ngxs/store';\n\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport { AppConfigState } from '../state/app-config.state';\n\n/** What the user PICKED. `system` defers to the OS. */\nexport type ThemeChoice = 'light' | 'dark' | 'system';\n\n/** What that resolves to right now — the only two the stylesheet knows. */\nexport type ResolvedTheme = 'light' | 'dark';\n\nconst CHOICES: readonly ThemeChoice[] = ['light', 'dark', 'system'];\n\n/**\n * A CACHE, never a source of truth.\n *\n * The authoritative value is `preferences.theme` in `/auth/me/settings`, which\n * the Profile -> Preferences tab already writes. This copy exists only so the\n * first paint of a reload is the right colour: the settings request is async,\n * so without it every load would start light and flip. It is deliberately NOT\n * stored via UserPreferencesService, which syncs its own bag to the server —\n * that would give one setting two server-side homes.\n */\nconst CACHE_KEY = 'coolms_theme';\nconst ACCENT_CACHE_KEY = 'coolms_accent';\n\n/**\n * Six-digit hex only, checked again HERE even though the server already vets it\n * on write. This value is substituted into a `--cms-*` custom property,\n * and a stored setting is data from the network like any other — the write-side\n * guard protects what the admin stores, this one protects what it renders.\n */\nconst HEX = /^#[0-9a-fA-F]{6}$/;\n\nfunction isChoice(v: unknown): v is ThemeChoice {\n return typeof v === 'string' && (CHOICES as readonly string[]).includes(v);\n}\n\nfunction isAccent(v: unknown): v is string {\n return typeof v === 'string' && HEX.test(v);\n}\n\n/**\n * Applies the user's theme by setting `data-theme` on the document element.\n *\n * The stylesheet does the rest: `:root[data-theme='dark']` re-points the\n * `--cms-*` palette and every colour in the admin arrives through those names.\n * Nothing here knows a single colour, which is the point — a second\n * theme is a block of tokens, not a code change.\n *\n * Follows the shape of UserCalendarPreferencesService and\n * CallOverlayPreferencesService: lazy one-shot load of `/auth/me/settings`,\n * cached for the SPA lifetime, with `update()` called by the Profile page after\n * a save so the change lands without a refetch.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeService {\n private readonly api = inject(IdentityApiClient);\n private readonly store = inject(Store);\n\n private readonly _choice = signal<ThemeChoice>(ThemeService.readCache());\n private readonly _systemDark = signal(false);\n private readonly _userAccent = signal<string | null>(ThemeService.readAccentCache());\n private readonly _platformAccent = signal<string | null>(null);\n\n private loadOnce$?: Observable<ThemeChoice>;\n\n /** The user's stored choice, including `system`. */\n readonly choice = this._choice.asReadonly();\n\n /** This user's personal override, independent of the deployment's. */\n readonly userAccent = this._userAccent.asReadonly();\n\n /** The deployment's brand accent from the API manifest, if it set one. */\n readonly platformAccent = this._platformAccent.asReadonly();\n\n /**\n * The colour actually painted: personal override, else the deployment's,\n * else null — which leaves the stylesheet's own accent in place.\n *\n * Three rungs and each is a real state. A user who has chosen nothing is\n * NOT the same as one who chose the deployment's colour: clear the\n * deployment's brand and the first user follows it, the second does not.\n */\n readonly accent = computed<string | null>(() => this._userAccent() ?? this._platformAccent());\n\n /** The choice with `system` resolved against the OS setting. */\n readonly resolved = computed<ResolvedTheme>(() => {\n const c = this._choice();\n\n return c === 'system' ? (this._systemDark() ? 'dark' : 'light') : c;\n });\n\n constructor() {\n // `matchMedia` is guarded because the unit suite and any SSR-ish\n // rendering path may not provide it; absent it, `system` means light.\n const mq = typeof window !== 'undefined' && window.matchMedia\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null;\n\n if (mq) {\n this._systemDark.set(mq.matches);\n // The OS can flip while the SPA is open, and `system` must follow it\n // live rather than only at load.\n mq.addEventListener('change', e => this._systemDark.set(e.matches));\n }\n\n effect(() => this.applyToDocument(this.resolved()));\n effect(() => this.applyAccent(this.accent()));\n }\n\n /**\n * One-shot load of the stored preference. Safe to call more than once and\n * safe for anonymous users — a failure leaves the cached/system value in\n * place rather than forcing light, so the login screen still matches the OS.\n */\n ensureLoaded(): Observable<ThemeChoice> {\n if (this.loadOnce$) return this.loadOnce$;\n\n // Read the deployment's colour FIRST and synchronously, so it is in\n // place whether or not the settings request succeeds — an anonymous\n // visitor on the login screen still sees the deployment's brand.\n this.readPlatformAccent();\n\n this.loadOnce$ = this.api.getSettings().pipe(\n map(all => {\n const prefs = all['preferences'] as Record<string, unknown> | undefined;\n // The accent rides along on the same response — it is a field of\n // the same section, so it costs no extra request.\n this.setAccent(isAccent(prefs?.['accentColor']) ? prefs['accentColor'] : null);\n\n return isChoice(prefs?.['theme']) ? prefs['theme'] : this._choice();\n }),\n tap(choice => this.set(choice)),\n catchError(() => of(this._choice())),\n shareReplay({ bufferSize: 1, refCount: false }),\n );\n\n return this.loadOnce$;\n }\n\n /**\n * Called by the Profile page after the Preferences tab saves, so the admin\n * re-themes on the spot. Ignores anything that is not a known choice —\n * the section PATCH returns the whole merged bag, not just this field.\n */\n update(value: unknown): void {\n if (isChoice(value)) this.set(value);\n }\n\n /**\n * Companion to {@link update} for the accent field of the same save.\n * An explicit null clears the override; anything malformed is ignored\n * rather than applied, so a bad value can never blank the admin's accent.\n */\n updateAccent(value: unknown): void {\n if (null === value || '' === value) this.setAccent(null);\n else if (isAccent(value)) this.setAccent(value);\n }\n\n private set(choice: ThemeChoice): void {\n this._choice.set(choice);\n try {\n localStorage.setItem(CACHE_KEY, choice);\n } catch {\n // A blocked or full localStorage costs the pre-paint hint, nothing\n // more — the server value still arrives and applies.\n }\n }\n\n /**\n * The manifest is loaded at bootstrap, well before the authenticated shell\n * calls this, so a snapshot read is enough and no subscription is needed.\n * A malformed value is ignored rather than applied — the container refuses\n * to build with one, but the manifest crosses the network all the same.\n */\n private readPlatformAccent(): void {\n const value = this.store.selectSnapshot(AppConfigState.manifest)?.platformDefaults?.accentColor;\n this._platformAccent.set(isAccent(value) ? value : null);\n }\n\n private setAccent(accent: string | null): void {\n this._userAccent.set(accent);\n try {\n if (accent) localStorage.setItem(ACCENT_CACHE_KEY, accent);\n else localStorage.removeItem(ACCENT_CACHE_KEY);\n } catch {\n // Same as the theme cache: losing it costs the pre-paint hint only.\n }\n }\n\n /**\n * Writes the override onto the document element, where an inline style\n * outranks every stylesheet rule — including the dark block — so one\n * declaration re-points the accent in both themes.\n *\n * Only --cms-accent is set, not the -hover/-light/-text members of the\n * family. Deriving those from an arbitrary user colour needs a colour model\n * the palette does not have yet, and guessing them would produce hover\n * states that clash with the very colour the user chose. Until then the\n * override moves the brand fill and leaves its supporting tones alone.\n */\n private applyAccent(accent: string | null): void {\n const root = document.documentElement;\n if (accent && HEX.test(accent)) root.style.setProperty('--cms-accent', accent);\n else root.style.removeProperty('--cms-accent');\n }\n\n private applyToDocument(theme: ResolvedTheme): void {\n // Written for BOTH values rather than only removing the attribute for\n // light, so the attribute is a reliable signal for anything that wants\n // to read the active theme, and so a future `[data-theme='light']`\n // block has something to hang on.\n document.documentElement.setAttribute('data-theme', theme);\n }\n\n private static readCache(): ThemeChoice {\n try {\n const raw = localStorage.getItem(CACHE_KEY);\n\n return isChoice(raw) ? raw : 'system';\n } catch {\n return 'system';\n }\n }\n\n private static readAccentCache(): string | null {\n try {\n const raw = localStorage.getItem(ACCENT_CACHE_KEY);\n\n return isAccent(raw) ? raw : null;\n } catch {\n return null;\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n/**\n * The platform's loading indicator: the exclamation mark from Cool!MS,\n * drawing itself.\n *\n * ## Why a mark and not a spinner\n *\n * A spinner says \"something is happening\" and nothing else; every product has\n * one and none of them are yours. The bang is already the one glyph in the\n * wordmark that carries the brand, so a loader built from it says whose\n * software is thinking — for free, in the place a user is already looking.\n *\n * ## Why one component rather than a snippet per surface\n *\n * It is meant for every wait long enough to notice: a dashboard assembling its\n * cards, an editor paginating a document, an explorer listing a folder. Those\n * are different teams' files, and a copied SVG in each is how three loaders\n * that no longer match each other happen.\n *\n * ## The animation\n *\n * The stem wipes upward and the dot lands after it, on a loop — the shape being\n * WRITTEN rather than spun. It is deliberately calm: a loader is background\n * furniture, and anything with a hard beat gets irritating on the third viewing.\n * Under `prefers-reduced-motion` it holds still and only breathes, because\n * motion sickness is not a stylistic preference.\n */\n@Component({\n selector: 'cms-loader',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"cms-loader\"\n [class.cms-loader--overlay]=\"overlay()\"\n [class.cms-loader--inline]=\"inline()\"\n role=\"status\"\n [attr.aria-label]=\"label() || 'Loading'\">\n <svg class=\"cms-loader__mark\" viewBox=\"0 0 24 48\" aria-hidden=\"true\">\n <defs>\n <clipPath [attr.id]=\"clipId\">\n <rect class=\"cms-loader__wipe\" x=\"0\" y=\"0\" width=\"24\" height=\"34\" />\n </clipPath>\n </defs>\n <!-- Ghost beneath: the mark stays legible as a SHAPE while the\n wipe crosses it, so the loader never looks broken mid-cycle. -->\n <rect class=\"cms-loader__ghost\" x=\"8\" y=\"2\" width=\"8\" height=\"30\" rx=\"4\" />\n <circle class=\"cms-loader__ghost\" cx=\"12\" cy=\"42\" r=\"4\" />\n\n <rect class=\"cms-loader__stem\" x=\"8\" y=\"2\" width=\"8\" height=\"30\" rx=\"4\"\n [attr.clip-path]=\"'url(#' + clipId + ')'\" />\n <circle class=\"cms-loader__dot\" cx=\"12\" cy=\"42\" r=\"4\" />\n </svg>\n @if (label()) {\n <span class=\"cms-loader__label\">{{ label() }}</span>\n }\n </div>\n `,\n styles: [`\n .cms-loader {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 24px;\n color: var(--cms-text-muted);\n }\n /* Fills whatever it is dropped into and paints the surface behind it,\n which is what makes it usable as a COVER for content that is not\n ready to be seen rather than only as a placeholder in empty space. */\n .cms-loader--overlay {\n position: absolute;\n inset: 0;\n z-index: 5;\n background: var(--cms-surface);\n }\n /* Inline: the mark sits BESIDE its label at text scale, for the waits\n that happen inside a row, a status bar or a button rather than in an\n empty panel. Same mark, so a page does not switch identities between\n its big wait and its small one. */\n .cms-loader--inline {\n flex-direction: row;\n gap: 8px;\n padding: 0;\n }\n .cms-loader--inline .cms-loader__mark {\n width: var(--cms-loader-size, 14px);\n }\n .cms-loader__mark {\n width: var(--cms-loader-size, 40px);\n height: auto;\n overflow: visible;\n }\n .cms-loader__ghost {\n fill: var(--cms-border);\n }\n .cms-loader__stem, .cms-loader__dot {\n fill: var(--cms-accent);\n }\n .cms-loader__wipe {\n animation: cms-loader-wipe 1.6s cubic-bezier(.65, 0, .35, 1) infinite;\n transform-origin: 12px 32px;\n }\n .cms-loader__dot {\n animation: cms-loader-dot 1.6s cubic-bezier(.65, 0, .35, 1) infinite;\n transform-origin: 12px 42px;\n }\n /* The stem is written from the base up: scaled about its own foot, so\n it grows out of the baseline instead of sliding into place. */\n @keyframes cms-loader-wipe {\n 0% { transform: scaleY(0); }\n 45%, 70% { transform: scaleY(1); }\n 100% { transform: scaleY(0); }\n }\n /* The dot lands after the stem has finished, then leaves with it. */\n @keyframes cms-loader-dot {\n 0%, 40% { opacity: 0; transform: scale(.4); }\n 55%, 70% { opacity: 1; transform: scale(1); }\n 100% { opacity: 0; transform: scale(.4); }\n }\n .cms-loader__label {\n font-size: .8125rem;\n }\n /* Motion sickness is not a stylistic preference: the mark holds its\n shape and only breathes. */\n @media (prefers-reduced-motion: reduce) {\n .cms-loader__wipe { animation: none; transform: scaleY(1); }\n .cms-loader__dot { animation: cms-loader-breathe 2s ease-in-out infinite; opacity: 1; }\n }\n @keyframes cms-loader-breathe {\n 0%, 100% { opacity: .35; }\n 50% { opacity: 1; }\n }\n `],\n})\nexport class CmsLoaderComponent {\n /** Optional caption. Absent renders the mark alone, which suits a small inline wait. */\n readonly label = input<string>('');\n\n /** Cover the positioned ancestor rather than sitting in the flow. */\n readonly overlay = input<boolean>(false);\n\n /**\n * Row layout at text scale, for a wait inside a row, a status bar or a\n * button. Override `--cms-loader-size` to tune the mark.\n */\n readonly inline = input<boolean>(false);\n\n /**\n * A clip-path needs a document-unique id, and this component is used more\n * than once per page — two loaders sharing an id makes the second one clip\n * against the first one's rectangle and stop animating.\n */\n protected readonly clipId = 'cms-loader-' + Math.random().toString(36).slice(2, 9);\n}\n","import { Injectable, signal } from '@angular/core';\n\n/**\n * Sidebar collapse state -- two independent dimensions:\n *\n * - `collapsed`: whole-sidebar icon-only rail toggle (the chevron at\n * the bottom of the sidebar). Per-device localStorage.\n * - `collapsedSections`: per-section collapse, keyed by the section\n * separator's node id. Used to fold a section's items under their\n * header so the nav doesn't endless-scroll once a tenant has lots\n * of modules. Per-device localStorage.\n * - `collapsedItems`: per-item inline-expand collapse, keyed by the\n * nav node's id. Mirrors `collapsedSections` but for sub-tree items\n * (a top-level item with children, e.g. Reports -> Process Report).\n * Absent id = expanded (children visible); present = collapsed.\n * Per-device localStorage.\n *\n * Storage failures (private mode, quota, disabled cookies) fall back\n * to defaulting un-collapsed -- the toggles still work for the session,\n * just don't persist.\n */\n@Injectable({ providedIn: 'root' })\nexport class SidebarStateService {\n private static readonly STORAGE_KEY = 'coolms.admin.sidebar.collapsed';\n private static readonly STORAGE_KEY_SECTIONS = 'coolms.admin.sidebar.collapsedSections';\n private static readonly STORAGE_KEY_ITEMS = 'coolms.admin.sidebar.collapsedItems';\n\n readonly collapsed = signal<boolean>(this.readPersisted());\n readonly collapsedSections = signal<Set<string>>(this.readSectionsPersisted());\n readonly collapsedItems = signal<Set<string>>(this.readItemsPersisted());\n\n toggle(): void {\n const next = !this.collapsed();\n this.collapsed.set(next);\n this.persist(next);\n }\n\n isSectionCollapsed(id: string): boolean {\n return this.collapsedSections().has(id);\n }\n\n toggleSection(id: string): void {\n const next = new Set(this.collapsedSections());\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n this.collapsedSections.set(next);\n this.persistSections(next);\n }\n\n /**\n * Force a section to be expanded. Used when navigation lands on\n * a route inside a manually-collapsed section -- it would be\n * confusing to leave the active item hidden. The user can\n * collapse the section again afterwards.\n */\n ensureExpanded(id: string): void {\n if (!this.collapsedSections().has(id)) return;\n const next = new Set(this.collapsedSections());\n next.delete(id);\n this.collapsedSections.set(next);\n this.persistSections(next);\n }\n\n // --- Per-item inline-expand (sub-tree items, e.g. a section's children) --\n\n isItemCollapsed(id: string): boolean {\n return this.collapsedItems().has(id);\n }\n\n toggleItem(id: string): void {\n const next = new Set(this.collapsedItems());\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n this.collapsedItems.set(next);\n this.persistItems(next);\n }\n\n private readPersisted(): boolean {\n try {\n return localStorage.getItem(SidebarStateService.STORAGE_KEY) === 'true';\n } catch {\n return false;\n }\n }\n\n private persist(value: boolean): void {\n try {\n localStorage.setItem(SidebarStateService.STORAGE_KEY, String(value));\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n\n private readSectionsPersisted(): Set<string> {\n try {\n const raw = localStorage.getItem(SidebarStateService.STORAGE_KEY_SECTIONS);\n if (raw === null) return new Set();\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return new Set();\n return new Set(parsed.filter((v): v is string => typeof v === 'string'));\n } catch {\n return new Set();\n }\n }\n\n private persistSections(value: Set<string>): void {\n try {\n localStorage.setItem(\n SidebarStateService.STORAGE_KEY_SECTIONS,\n JSON.stringify(Array.from(value)),\n );\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n\n private readItemsPersisted(): Set<string> {\n try {\n const raw = localStorage.getItem(SidebarStateService.STORAGE_KEY_ITEMS);\n if (raw === null) return new Set();\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return new Set();\n return new Set(parsed.filter((v): v is string => typeof v === 'string'));\n } catch {\n return new Set();\n }\n }\n\n private persistItems(value: Set<string>): void {\n try {\n localStorage.setItem(\n SidebarStateService.STORAGE_KEY_ITEMS,\n JSON.stringify(Array.from(value)),\n );\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { type Observable } from 'rxjs';\n\n/**\n * One row returned by the `/api/entity-search` endpoint. `id` is\n * what the picker emits via its `valueChange` output (and what the\n * template's persisted context carries). `label` is the primary\n * display line. `secondary` is an optional second line for\n * disambiguation (email, slug, etc.) — the backend omits the key\n * when there is nothing to show; receiving `null` from a custom\n * resolver is normalized to `undefined` here.\n */\nexport interface EntitySearchResult {\n readonly id: string | number;\n readonly label: string;\n readonly secondary?: string;\n}\n\n/**\n * HTTP client for the Phase 2a `/api/entity-search` endpoint.\n * Returns a stream of search results for the given entity FQCN.\n * Permission filtering happens at the backend resolver layer —\n * this service is a thin RPC.\n */\n@Injectable({ providedIn: 'root' })\nexport class EntitySearchService {\n private readonly http = inject(HttpClient);\n\n /**\n * @param entityType Fully-qualified PHP class name of the entity\n * being searched (e.g. the backend's User entity).\n * @param query Free-text search input. Empty string is\n * valid — the backend returns the default\n * listing for that entity type.\n * @param limit Max rows to return; clamped server-side to\n * [1, 100].\n */\n search(entityType: string, query: string, limit = 20): Observable<EntitySearchResult[]> {\n const params = new HttpParams()\n .set('type', entityType)\n .set('q', query)\n .set('limit', String(limit));\n\n return this.http.get<EntitySearchResult[]>('/api/entity-search', { params });\n }\n}\n","import { inject, Injectable, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { catchError, map, type Observable, of } from 'rxjs';\nimport { Logout } from '../auth/auth.actions';\nimport { type NaviGraphNode, type NaviGraphNodeFlat } from './navi-graph.types';\nimport { ErrorHandlerService } from '../errors/error-handler.service';\n\n/**\n * Loads, caches, and drives admin NaviGraph trees.\n *\n * Usage:\n * naviGraphService.loadAdminNav(url).subscribe();\n * const items = naviGraphService.adminNav(); // signal<NaviGraphNode[]>\n * naviGraphService.handleClick(node);\n */\n@Injectable({ providedIn: 'root' })\nexport class NaviGraphService {\n private readonly http = inject(HttpClient);\n private readonly router = inject(Router);\n private readonly store = inject(Store);\n private readonly errors = inject(ErrorHandlerService);\n\n /** Resolved root nodes for navi.admin (sidebar). */\n readonly adminNav = signal<NaviGraphNode[]>([]);\n\n /** Resolved root nodes for navi.admin.topbar. */\n readonly topbarNav = signal<NaviGraphNode[]>([]);\n\n private readonly cache = new Map<string, NaviGraphNode[]>();\n\n // -------------------------------------------------------------------------\n\n loadAdminNav(url: string): Observable<NaviGraphNode[]> {\n return this.loadTree(url).pipe(\n map(tree => { this.adminNav.set(tree); return tree; }),\n );\n }\n\n loadTopbarNav(url: string): Observable<NaviGraphNode[]> {\n return this.loadTree(url).pipe(\n map(tree => { this.topbarNav.set(tree); return tree; }),\n );\n }\n\n // -------------------------------------------------------------------------\n\n /**\n * Handle a nav-node click.\n *\n * Routing logic based on meta.target:\n * 'action.logout' — dispatch Logout\n * '_blank' — window.open (uses node.path as href)\n * default — router.navigate with meta.routerLink or node.path\n */\n handleClick(node: NaviGraphNode): void {\n const target = node.meta.target;\n\n if (target === 'action.logout') {\n this.store.dispatch(new Logout()).subscribe(() => {\n void this.router.navigate(['/login']);\n });\n return;\n }\n\n if (target === '_blank') {\n window.open(node.meta.routerLink ?? node.path, '_blank', 'noopener');\n return;\n }\n\n const link = node.meta.routerLink ?? node.path;\n void this.router.navigate([link]);\n }\n\n // -------------------------------------------------------------------------\n\n /**\n * Evaluate `showWhen` meta condition against a record.\n * Nodes with no `showWhen` are always visible.\n * Supports nested AND/OR logic: { and: [...] } / { or: [...] }\n */\n isVisible(node: NaviGraphNode, record: Record<string, unknown>): boolean {\n const showWhen = node.meta['showWhen'] as Record<string, unknown> | undefined;\n if (!showWhen) return true;\n return this.evalCondition(showWhen, record);\n }\n\n /**\n * Standalone `showWhen` evaluator for callers that don't have a\n * full `NaviGraphNode` (e.g. a grid's row context menu — a\n * `rowAction.showWhen` predicate is evaluated against the row's\n * own projection). `null`/`undefined` predicates default to\n * visible, mirroring `isVisible`'s \"no rule = always shown\"\n * semantics.\n */\n matchesShowWhen(showWhen: Record<string, unknown> | undefined | null, record: Record<string, unknown>): boolean {\n if (!showWhen) return true;\n return this.evalCondition(showWhen, record);\n }\n\n private evalCondition(cond: Record<string, unknown>, record: Record<string, unknown>): boolean {\n if ('and' in cond) {\n return (cond['and'] as Record<string, unknown>[])\n .every(c => this.evalCondition(c, record));\n }\n if ('or' in cond) {\n return (cond['or'] as Record<string, unknown>[])\n .some(c => this.evalCondition(c, record));\n }\n // Strict missing-field semantics. A leaf predicate\n // referencing a field absent from the record evaluates to\n // false (fail-loud) instead of the prior op-dependent lax\n // behavior that returned true for ne/nin/unknown ops. Aligns\n // with CSS attribute selectors / JSONPath / React conditional\n // rendering conventions.\n const field = cond['field'] as string;\n if (!(field in record)) return false;\n const val = record[field];\n switch (cond['op']) {\n case 'eq': return val === cond['value'];\n case 'ne': return val !== cond['value'];\n case 'in': return Array.isArray(cond['value']) && (cond['value'] as unknown[]).includes(val);\n case 'nin': return Array.isArray(cond['value']) && !(cond['value'] as unknown[]).includes(val);\n // Prefix match for strings — primary use case is filtering\n // actions by MIME-type major (e.g., \"image/\" matches all\n // image MIMEs without enumerating each subtype).\n case 'startsWith':\n return typeof val === 'string'\n && typeof cond['value'] === 'string'\n && val.startsWith(cond['value']);\n // Strict: unknown / typo op (e.g. 'equals' instead of 'eq')\n // never matches.\n default: return false;\n }\n }\n\n /**\n * Fetch a graph endpoint and return a nested tree.\n * Handles both compact JSON-LD IRIs (`member`) and\n * the legacy Hydra key (`hydra:member`).\n * Results are cached by URL for the lifetime of the service instance.\n */\n loadTree(url: string): Observable<NaviGraphNode[]> {\n if (this.cache.has(url)) {\n return of(this.cache.get(url)!);\n }\n\n return this.http.get<Record<string, unknown>>(url, {\n headers: { Accept: 'application/ld+json' },\n }).pipe(\n map(r => {\n const raw = r['member'] ?? r['hydra:member'] ?? [];\n const flat: NaviGraphNodeFlat[] = Array.isArray(raw) ? raw as NaviGraphNodeFlat[] : [];\n const tree = this.buildTree(flat);\n this.cache.set(url, tree);\n return tree;\n }),\n catchError(err => {\n console.error('[NaviGraph] loadTree failed for', url, this.errors.humanize(err));\n return of([]);\n }),\n );\n }\n\n /**\n * Build a nested tree from a flat array of nodes.\n * Nodes are sorted by sortOrder at each level.\n */\n private buildTree(flat: NaviGraphNodeFlat[]): NaviGraphNode[] {\n if (!flat.length) {\n return [];\n }\n\n const map = new Map<string, NaviGraphNode>(\n flat.map(n => [n.id, { ...n, children: [] }]),\n );\n const roots: NaviGraphNode[] = [];\n\n for (const node of map.values()) {\n if (node.parentId && map.has(node.parentId)) {\n map.get(node.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n const sort = (nodes: NaviGraphNode[]): void => {\n nodes.sort((a, b) => a.sortOrder - b.sortOrder);\n nodes.forEach(n => n.children?.length && sort(n.children));\n };\n sort(roots);\n\n return roots;\n }\n}\n","// --- NaviGraph API types ------------------------------------------------------\n// Mirror the PHP NaviGraphNodeResource DTO exactly. No `any` allowed.\n\n/**\n * Well-known click target values embedded in meta.target.\n * 'action.logout' — dispatch Logout action\n * 'route' — navigate via routerLink (default when target absent)\n * '_blank' — open in a new tab (hrefOverride must be set)\n */\n// `& {}` rather than a bare `| string`, which absorbs the three literals and\n// leaves the type meaning \"any string\" -- the docblock above would then be\n// describing a union the compiler had already erased.\nexport type NaviGraphTarget = 'action.logout' | 'route' | '_blank' | (string & {});\n\nexport interface NaviGraphMeta {\n readonly component?: string; // e.g. 'identity.signout'\n readonly icon?: string; // icon name hint for the renderer\n readonly routerLink?: string; // Angular router path, e.g. 'navi'\n readonly target?: NaviGraphTarget; // click behaviour override\n readonly requiredRole?: string; // backend-side role guard\n readonly voterAttribute?: string; // PHP-side Voter guard\n readonly contributor?: string; // set by NaviGraphSeeder (read-only)\n readonly [key: string]: unknown;\n}\n\n/** Flat node as returned from GET /navi/trees/{slug}/graph */\nexport interface NaviGraphNodeFlat {\n readonly id: string;\n readonly path: string;\n readonly title: string;\n readonly parentId: string | null;\n readonly sortOrder: number;\n readonly isActive: boolean;\n readonly isVisible: boolean;\n readonly meta: NaviGraphMeta;\n}\n\n/** Same node enriched with resolved children after client-side tree build */\nexport interface NaviGraphNode extends NaviGraphNodeFlat {\n readonly children: NaviGraphNode[];\n}\n","import { Injectable, type Type } from '@angular/core';\n\n/**\n * Registry mapping string keys to Angular component classes.\n *\n * Backed by a static Map so components registered at module load time\n * (e.g. in app.config.ts) are visible to all injected instances.\n *\n * Usage — static (app.config.ts / bootstrap time):\n * ComponentRegistry.register('MediaLibraryPage', MediaLibraryPage);\n *\n * Usage — injected (SlotComponent / runtime):\n * private readonly registry = inject(ComponentRegistry);\n * const cls = this.registry.get('MediaLibraryPage');\n */\n@Injectable({ providedIn: 'root' })\nexport class ComponentRegistry {\n private static readonly map = new Map<string, Type<unknown>>();\n\n // -- Static API (bootstrap / app.config.ts) -------------------------------\n\n static register(key: string, component: Type<unknown>): void {\n ComponentRegistry.map.set(key, component);\n }\n\n static get(key: string): Type<unknown> | undefined {\n return ComponentRegistry.map.get(key);\n }\n\n static has(key: string): boolean {\n return ComponentRegistry.map.has(key);\n }\n\n static keys(): string[] {\n return Array.from(ComponentRegistry.map.keys());\n }\n\n // -- Instance API (injectable) ---------------------------------------------\n\n register(key: string, component: Type<unknown>): void {\n ComponentRegistry.register(key, component);\n }\n\n get(key: string): Type<unknown> | null {\n return ComponentRegistry.get(key) ?? null;\n }\n\n has(key: string): boolean {\n return ComponentRegistry.has(key);\n }\n\n keys(): string[] {\n return ComponentRegistry.keys();\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * The one fact the section interceptor needs: which site the operator is\n * currently working in.\n *\n * `core` is auth, config, errors and interceptors -- the layer everything else\n * is built on -- so it must not import a feature. The obvious-looking fix,\n * moving `SectionState` into core, is the wrong one: sections are a CMS domain\n * concept, and hoisting domain state into the base layer trades one bad\n * direction for another. So core declares the little it needs and the module\n * that owns the state provides it, bound in the composition root.\n *\n * Read SYNCHRONOUSLY on purpose. An interceptor decides whether to stamp a\n * header while building the request; it cannot wait on a stream.\n */\nexport interface CurrentSectionPort {\n /** Slug of the active section, or null when the operator picked none. */\n currentSlug(): string | null;\n}\n\n/**\n * Optional by design: an application assembled without a Sections module binds\n * nothing and simply sends no `X-CoolMS-Section` header, which is exactly what\n * a single-site install wants.\n */\nexport const CURRENT_SECTION = new InjectionToken<CurrentSectionPort>(\n 'coolms.current-section',\n);\n","import { inject } from '@angular/core';\nimport { type HttpInterceptorFn } from '@angular/common/http';\nimport { CURRENT_SECTION } from '../section/current-section.port';\n\n/**\n * Phase H7 -- admin Site Selector.\n *\n * Stamps `X-CoolMS-Section: <slug>` on outgoing `/api/v1/*` requests when\n * the admin has picked a section from the Site Selector dropdown. The backend\n * honours it for AUTHENTICATED requests only -- anonymous traffic cannot pivot\n * the active section.\n *\n * What the header actually reaches, counted rather than intended: exactly two\n * readers act on it, `CreatePageProcessor` and `CreateCollectionProcessor`,\n * each choosing WHICH SITE a newly created page or collection lands in -- and,\n * for a page, that section's default locale and slug-naming policy. Nothing\n * else is scoped by it. Media spaces list every active section regardless, and\n * every other reader of `_coolms_section` sits on the public SSR surface,\n * which resolves from host+path and never sees this header.\n *\n * This block used to say the backend \"scopes site-specific API providers\n * (pages, media spaces, etc.)\". That described where was heading --\n * H4/H5, multisite Document and Media, never landed -- not what it does. Left\n * recorded because a docstring naming an intended destination reads exactly\n * like one naming a shipped feature, and this one misdescribed the control for\n * three months.\n *\n * Skipped:\n * - non-API URLs (no section context needed for SSR/static)\n * - `/auth/*` endpoints (login/refresh are pre-session; the header would\n * be ignored by the backend anyway, but skipping keeps wire traffic clean)\n * - requests that already carry the header (programmatic overrides win)\n */\nexport const sectionInterceptor: HttpInterceptorFn = (req, next) => {\n if (!shouldStamp(req.url)) {\n return next(req);\n }\n if (req.headers.has('X-CoolMS-Section')) {\n return next(req);\n }\n\n // Optional: no Sections module installed means no section to stamp.\n const slug = inject(CURRENT_SECTION, { optional: true })?.currentSlug() ?? null;\n if (!slug) return next(req);\n\n return next(req.clone({ setHeaders: { 'X-CoolMS-Section': slug } }));\n};\n\nfunction shouldStamp(url: string): boolean {\n // Strip protocol/host so absolute and relative URLs are treated alike.\n const path = url.replace(/^https?:\\/\\/[^/]+/, '');\n if (!path.startsWith('/api/v1/')) return false;\n if (path.startsWith('/api/v1/auth/')) return false;\n return true;\n}\n","/**\n * `@coolms/core-angular` -- the CoolMS client runtime for Angular.\n *\n * Everything a CoolMS front-end needs before it has any UI: the session and its\n * refresh/cross-tab machinery, the boot manifest and app config, theme and user\n * preferences, error handling, the HTTP interceptors, and the wire types the\n * backend's form-render and navi-graph endpoints emit.\n *\n * It is deliberately NOT \"the admin's core\": nothing here is admin chrome. It is\n * also not a general Angular library -- every endpoint it speaks is a CoolMS\n * one, so it is worthless without a CoolMS backend. The layering is\n *\n * @coolms/core-angular -> @coolms/admin-ui-angular -> @coolms/x-ui-angular\n *\n * and the reason it must be a package rather than app code is federation: the\n * kit and every remote need this at runtime as ONE `singleton`, and a remote\n * cannot import from its host.\n *\n * This barrel is the whole public surface. Nothing outside `core/` may deep-link\n * past it -- eslint enforces that, so this file is what \"core exports\" MEANS. A\n * symbol not listed here is internal, and adding one is a deliberate act.\n *\n * The single component (`LoginComponent`) is here on purpose: core owns the auth\n * lifecycle, so it ships a working sign-in rather than leaving every consumer to\n * rebuild one against endpoints only core knows.\n */\n\n// -- Wire types + the API client core owns ------------------------------------\nexport * from './api/api-manifest.types';\nexport * from './api/auth.types';\nexport * from './api/form-render.types';\nexport * from './api/hydra.types';\nexport * from './api/identity-api.client';\nexport * from './api/realtime-token.client';\n\n// -- Session ------------------------------------------------------------------\nexport * from './auth/auth.actions';\nexport * from './auth/auth.state';\nexport * from './auth/auth.guard';\nexport * from './auth/login-page.guard';\nexport * from './auth/auth.interceptor';\nexport * from './auth/auth-refresh.coordinator';\nexport * from './auth/cross-tab-auth-sync.service';\nexport * from './auth/login/login.component';\n\n// -- Boot + configuration -----------------------------------------------------\nexport * from './bootstrap/app-init.service';\nexport * from './config/config.service';\nexport * from './state/app-config.state';\n\n// -- Cross-cutting services ---------------------------------------------------\nexport * from './errors/error-handler.service';\nexport * from './theme/theme.service';\n// The platform's loader mark. It lives here rather than in the UI kit\n// because the kit is not the lowest layer that needs it: the editor sits\n// ABOVE the kit and still has to show it, and a second copy of a brand\n// mark is worse than a component in a runtime package.\nexport * from './loader/cms-loader.component';\nexport * from './services/user-preferences.service';\nexport * from './services/user-preferences.types';\nexport * from './sidebar/sidebar-state.service';\nexport * from './entity-search/entity-search.service';\n\n// -- Navigation graph ---------------------------------------------------------\nexport * from './navi-graph/navi-graph.service';\nexport * from './navi-graph/navi-graph.types';\nexport * from './navi-graph/component-registry';\n\n// -- Section scoping ----------------------------------------------------------\n// The interceptor stamps `X-CoolMS-Section`; the port is how the app tells it\n// which section is active without core naming a feature.\nexport * from './interceptors/section.interceptor';\nexport * from './section/current-section.port';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["STORAGE_KEY","switchMap","tap","map","catchError","shareReplay"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"coolms-core-angular.mjs","names":["STORAGE_KEY","tap","switchMap","STORAGE_KEY","map","tap","catchError","shareReplay"],"sources":["../../src/api/api-manifest.types.ts","../../src/state/app-config.state.ts","../../src/api/identity-api.client.ts","../../src/api/realtime-token.client.ts","../../src/auth/auth.actions.ts","../../src/services/user-preferences.service.ts","../../src/auth/auth.state.ts","../../src/auth/auth-refresh.coordinator.ts","../../src/auth/auth.interceptor.ts","../../src/auth/cross-tab-auth-sync.service.ts","../../src/bootstrap/app-init.service.ts","../../src/auth/auth.guard.ts","../../src/auth/login-page.guard.ts","../../src/auth/elevation.types.ts","../../src/auth/elevation-prompt.port.ts","../../src/auth/elevation.service.ts","../../src/errors/error-handler.service.ts","../../src/auth/elevation.interceptor.ts","../../src/auth/login/login.component.ts","../../src/auth/login/login.component.html","../../src/config/config.service.ts","../../src/theme/theme.service.ts","../../src/loader/cms-loader.component.ts","../../src/sidebar/sidebar-state.service.ts","../../src/entity-search/entity-search.service.ts","../../src/navi-graph/navi-graph.service.ts","../../src/navi-graph/component-registry.ts","../../src/contracts/console/console-v1.ts","../../src/contracts/console/console-host.ts","../../src/section/current-section.port.ts","../../src/interceptors/section.interceptor.ts"],"sourcesContent":["// Typed contract -- must mirror ApiManifest PHP VOs exactly.\n// No `any` anywhere.\n\nexport interface AuthApiManifest {\n readonly login: string;\n readonly logout: string;\n readonly refresh: string;\n readonly register: string;\n readonly me: string;\n readonly usersApi?: string; // GET /api/v1/auth/users\n readonly groupsApi?: string; // GET /api/v1/auth/groups\n readonly preferencesUrl?: string; // PATCH /api/v1/auth/me/preferences\n readonly groupFormId?: string | null;\n readonly userFormId?: string | null;\n}\n\nexport interface SectionApiManifest {\n readonly list: string;\n readonly create: string;\n readonly item: string; // URL pattern: /api/v1/sections/{id}\n readonly update: string;\n readonly delete: string;\n readonly formId?: string;\n readonly apply?: string; // POST /api/v1/sections/_apply (H9)\n}\n\nexport interface NaviApiManifest {\n readonly treesList: string;\n readonly treesCreate: string;\n readonly treesItem: string; // pattern: /api/v1/navi/trees/{slug}\n readonly nodesList: string;\n readonly nodesCreate: string;\n readonly nodesItem: string; // pattern: /api/v1/navi/nodes/{id}\n readonly nodesReorder: string; // PATCH /api/v1/navi/nodes/reorder\n readonly nodesMove: string; // pattern: /api/v1/navi/nodes/{id}/move\n readonly nodesByTree: string; // pattern: /api/v1/navi/trees/{slug}/nodes\n readonly treesFormId?: string;\n readonly nodesFormId?: string;\n readonly adminNavGraph?: string; // /api/v1/navi/trees/navi.admin/graph\n readonly topbarNavGraph?: string; // /api/v1/navi/trees/navi.admin.topbar/graph\n readonly toolbarVfsGraph?: string; // /api/v1/navi/trees/navi.toolbar.vfs/graph\n readonly graphBySlug?: string; // URL pattern: /api/v1/navi/trees/{slug}/graph\n}\n\nexport interface ContentApiManifest {\n // Page + variant CRUD lives on the generic VFS node endpoints, filtered\n // by mime where needed; what stays here is Content-owned surface.\n readonly variantPublishUrl: string; // pattern: /api/v1/content/variants/{id}/publish\n // Pages admin. Optional because a client built against an older backend\n // will not receive them, and the affected control degrades (an empty\n // space list, a picker with only \"Default\") rather than throwing.\n //\n // The five `articleXxxUrl` fields that stood here are GONE with the\n // endpoints they named. Placement destinations now\n // come from `/content/pages/surfaces` -- still never hardcoded on the\n // client, because a surface is configuration, not a product concept.\n readonly pageSpacesUrl?: string; // /api/v1/content/pages/spaces\n readonly pageTypesUrl?: string; // /api/v1/content/page-types\n}\n\nexport interface DataGridApiManifest {\n readonly configBase: string; // base URL: /api/v1/datagrids\n}\n\nexport interface TerminalApiManifest {\n readonly executeUrl: string; // POST /api/v1/terminal/execute (SSE)\n readonly completeUrl: string; // POST /api/v1/terminal/complete\n}\n\nexport interface MediaApiManifest {\n readonly listUrl: string; // GET /media\n readonly itemUrl: string; // pattern: /api/v1/media/{id}\n readonly permissionsUrl: string; // pattern: /api/v1/media/{id}/permissions\n readonly regenerateUrl: string; // pattern: /api/v1/media/{id}/regenerate\n readonly resizeUrl: string; // pattern: /media/resize/{id}\n readonly collectionsUrl: string; // GET /api/v1/media/collections\n readonly collectionsCreateUrl: string; // POST /api/v1/media/collections\n readonly collectionPermissionsUrl: string; // PATCH /api/v1/media/collections/permissions\n readonly moveUrl?: string; // pattern: /api/v1/media/{id}/move\n readonly spacesUrl?: string; // GET /api/v1/media/spaces\n}\n\nexport interface VfsApiManifest {\n readonly fileContentUrl: string; // GET/PUT /api/v1/vfs/files/content?path={path}\n readonly binaryWriteUrl: string; // POST /api/v1/vfs/files/binary (multipart)\n}\n\n/**\n * Document module URL section. Today the only entry is `spacesUrl`;\n * the struct is reserved so future Document-module URLs can land\n * here without a new manifest section.\n */\nexport interface DocumentApiManifest {\n readonly spacesUrl?: string; // GET /api/v1/document/spaces\n\n /**\n * !! Carried here rather than derived from `spacesUrl`. Concatenating\n * `/available` onto that would couple the client to a URL shape the router\n * owns, and it would keep working until the route moved -- then fail as a\n * 404 the UI reports as \"no sites available\", which is indistinguishable\n * from the true empty answer.\n *\n * !! ABSENT ARRIVES AS `''`, NOT `undefined`. The producer declares these\n * as non-nullable strings defaulting to empty, so an installation that has\n * not set them sends an empty string and the `?` here never fires.\n *\n * Guard on FALSINESS -- `!url`, `Boolean(url)`. The dangerous rewrite is\n * `url === undefined` (or `!== undefined`), which reads as more precise and\n * treats `''` as present, handing a caller a request to the empty string.\n *\n * !! A `?? fallback` is NOT the hazard, though it looks like the obvious\n * one: `'' ?? x` is `''`, so a fallback never fires on the value this\n * producer actually sends. Measured by mutation -- adding one changed\n * nothing, changing the guard broke three assertions.\n */\n readonly spacesAvailableUrl?: string; // GET /api/v1/document/spaces/available\n readonly spaceEnablementUrl?: string; // POST /api/v1/document/spaces/enablement\n\n /**\n * FQCN of the entity a filter-mode audience can be built from.\n *\n * !! The SERVER owns this and the client must not know it. The generation\n * wizard offers Filter mode only when a template's context schema\n * references this type, and it used to compare against a constant compiled\n * into this bundle -- which shipped the consuming application's class name\n * inside a published package, and tied that package to an installation\n * having that exact class.\n *\n * !! Absent arrives as `''`, like the urls above. Treat empty as **filter\n * mode unavailable**, never as a reason to fall back to a remembered\n * value: the server is the thing that rejects every other type, so a guess\n * here offers a mode the server will refuse.\n */\n readonly filterAudienceEntity?: string;\n}\n\nexport interface DynamicEntityApiManifest {\n readonly typesUrl: string; // GET /api/v1/dynamic-entity/types\n readonly fieldDefinitionsUrl: string; // POST /api/v1/field/definitions\n readonly fieldDefinitionUrl: string; // PUT/PATCH/DELETE /api/v1/field/definitions/{id}\n readonly fieldDefinitionsReorderUrl: string; // PATCH /api/v1/field/definitions/reorder\n readonly toolbarNaviGraphUrl: string; // GET /navi/trees/navi.toolbar.dynamic-entity/graph\n readonly constraintsUrl: string; // GET /api/v1/dynamic-entity/constraints\n readonly recordsUrl: string; // pattern: GET/POST /api/v1/dynamic-entity/{type}/records\n readonly recordUrl: string; // pattern: GET/PATCH/DELETE .../records/{id}\n readonly recordsByTypeUrl: string; // pattern: GET/POST /api/v1/dynamic-entity/{type}/records\n readonly typeUrl: string; // GET/PATCH/DELETE .../types/{id}\n readonly typesCreateUrl: string; // POST .../types (same as typesUrl)\n readonly formTypesUrl?: string; // GET /api/v1/field/form-types\n readonly typeByAliasUrl?: string; // GET /api/v1/dynamic-entity/types/{alias}\n}\n\nexport interface DomainExplorerApiManifest {\n readonly entitiesUrl: string; // GET /api/v1/domain-explorer/entities\n readonly entityUrl: string; // pattern: /api/v1/domain-explorer/entities/{entityAlias}\n readonly toolbarNaviGraphUrl: string; // GET /navi/trees/navi.toolbar.domain_explorer/graph\n readonly domainExplorerToolbarUrl: string; // alias for toolbarNaviGraphUrl (backward compat)\n readonly entityFieldsDataGridUrl?: string; // GET /api/v1/datagrids/domain_explorer:entity_fields\n readonly typeFieldsDataGridUrl?: string; // GET /api/v1/datagrids/domain_explorer:type_fields\n}\n\nexport interface IdentityApiManifest {\n readonly usersUrl: string; // GET /auth/users\n readonly userUrl: string; // /auth/users/{id} pattern\n readonly groupsUrl: string; // GET /auth/groups\n readonly groupUrl: string; // /auth/groups/{id} pattern\n readonly meUrl: string; // GET /auth/me\n readonly assignGroupsUrl: string; // /auth/users/{id}/groups pattern\n readonly avatarUploadUrl: string; // POST /auth/me/avatar\n readonly settingsUrl: string; // GET /auth/me/settings\n readonly settingsSectionsUrl: string; // GET /auth/me/settings/sections\n readonly settingsSectionUrl: string; // PATCH /auth/me/settings/{section} pattern\n readonly colorUrl: string; // PATCH /auth/me/color\n readonly rolesUrl?: string; // GET /api/v1/identity/roles\n readonly elevationUrl?: string; // GET / POST / DELETE /api/v1/auth/elevation\n // The deletion screens: the list, the declarations, and the per-account\n // patterns the user page reads and acts on. Optional: an older server\n // does not send them and the client then shows no deletion surfaces.\n readonly deletionsUrl?: string; // GET /auth/deletions (RQL)\n readonly footprintsUrl?: string; // GET /auth/footprints\n readonly userDeletionUrl?: string; // GET / DELETE /auth/users/{id}/deletion pattern\n readonly userLegalHoldsUrl?: string; // GET / POST /auth/users/{id}/legal-holds pattern; DELETE .../{holdId}\n readonly holdsSettingsBlock?: string; // the holds register's settings block key\n readonly legalHoldFormId?: string | null; // the form rendered to place a hold\n}\n\n/**\n * Editor sub-manifest emitted by the backend's Editor module. The\n * frontend bridge (`@coolms/editor-angular`) consumes this through the\n * `EDITOR_MANIFEST_PROVIDER` token.\n *\n * schemaVersion 2 (sub-prompt D) replaced the editorId axis with a profile\n * axis: `manifest.editor.profiles[name]` returns a slice with the\n * pre-resolved contributor list AND the storage allow-list a sanitizer\n * enforces on save.\n */\nexport interface EditorApiManifestEntry {\n readonly id: string;\n readonly group: string;\n readonly priority: number;\n readonly icon: string;\n readonly label: string;\n readonly actionType: string;\n readonly actionParams: Readonly<Record<string, unknown>>;\n readonly extensions: ReadonlyArray<string>;\n readonly stateKeys: ReadonlyArray<string>;\n readonly shortcut?: string | null;\n // `slashable` (derived from `group`) flags the\n // entries the `/`-command palette offers; `keywords` are extra fuzzy-search\n // aliases. Both serialized into `GET /api/v1/theme/config`.\n readonly slashable?: boolean;\n readonly keywords?: ReadonlyArray<string>;\n}\n\nexport interface EditorApiManifestProfile {\n readonly contributors: ReadonlyArray<EditorApiManifestEntry>;\n readonly allowedWidgets: ReadonlyArray<string>;\n}\n\nexport interface EditorApiManifest {\n readonly schemaVersion: number;\n readonly profiles: Readonly<Record<string, EditorApiManifestProfile>>;\n}\n\n/**\n * Viewer manifest emitted by the backend's Document module.\n * Each viewer declares the MIME types / extensions it handles, the\n * Angular component selector to dispatch into, and one config blob per\n * profile. Profile config is open-ended -- every viewer interprets the\n * keys it understands and ignores the rest.\n */\nexport interface ViewerProfileApiManifest {\n readonly key: string;\n readonly config: Readonly<Record<string, unknown>>;\n}\n\nexport interface ViewerDefinitionApiManifest {\n readonly key: string;\n readonly mimeTypes: ReadonlyArray<string>;\n readonly extensions: ReadonlyArray<string>;\n readonly component: string;\n readonly profiles: Readonly<Record<string, ViewerProfileApiManifest>>;\n}\n\nexport interface ViewerApiManifest {\n readonly schemaVersion: number;\n readonly viewers: Readonly<Record<string, ViewerDefinitionApiManifest>>;\n}\n\n/**\n * Platform-wide default user-facing settings. The\n * fallback floor for anonymous / pre-login rendering: timezone, date /\n * time format (CLDR tokens) and week start come from the deployment's\n * config, not an FE-hardcoded Western default. `locale` is the default\n * locale, previously absent from the manifest.\n */\nexport interface PlatformDefaults {\n readonly locale: string;\n readonly timezone: string;\n readonly dateFormat: string;\n readonly timeFormat: '12h' | '24h';\n readonly weekStart: 'monday' | 'sunday';\n /**\n * Deployment brand accent as `#rrggbb`, or null/absent to keep the\n * stylesheet's own. Sits BELOW a user's personal accentColor.\n */\n readonly accentColor?: string | null;\n}\n\n/**\n * The host contracts in force here (the platform rule: hosts implement\n * contracts, modules offer entries): for each contract, what the one\n * installed theme implementing it declares, and the installed modules whose\n * entries the installer matched against it. A host mounts a module only when\n * it is listed.\n */\nexport interface UiApiManifest {\n /** The contracts in force, name -> MAJOR.MINOR (`{ console: '1.0' }`): one installed theme implements each. */\n readonly contracts: Readonly<Record<string, string>>;\n /** Which installed theme implements each contract, name -> theme slug. */\n readonly hosts?: Readonly<Record<string, string>>;\n readonly modules: ReadonlyArray<UiModuleEntryManifest>;\n}\n\nexport interface UiModuleEntryManifest {\n readonly module: string;\n readonly contract: string;\n readonly range: string;\n readonly framework: string;\n}\n\nexport interface ApiManifest {\n readonly apiBase: string;\n readonly ui?: UiApiManifest;\n readonly configBase?: string; // GET /api/v1/config/{type}/{id}\n readonly auth?: AuthApiManifest;\n readonly identity?: IdentityApiManifest;\n readonly sections?: SectionApiManifest;\n readonly navi?: NaviApiManifest;\n readonly content?: ContentApiManifest;\n readonly dataGrid?: DataGridApiManifest;\n readonly terminal?: TerminalApiManifest;\n readonly media?: MediaApiManifest;\n readonly document?: DocumentApiManifest;\n readonly vfs?: VfsApiManifest;\n readonly dynamicEntity?: DynamicEntityApiManifest;\n readonly domainExplorer?: DomainExplorerApiManifest;\n readonly editor?: EditorApiManifest;\n readonly viewers?: ViewerApiManifest;\n readonly supportedLocales?: Array<{ code: string; label: string }>;\n readonly platformDefaults?: PlatformDefaults;\n}\n\nexport interface ThemeConfigResponse {\n readonly slug?: string | null;\n readonly feStack?: string | null;\n readonly spaFramework?: string | null;\n readonly assetsUrl?: string | null;\n readonly manifest: ApiManifest;\n}\n\n/**\n * Replaces {param} tokens in a URL pattern with encoded values.\n *\n * @example\n * resolvePattern(manifest.sections!.item, { id: '123' })\n * // -> '/api/v1/sections/123'\n */\nexport function resolvePattern(pattern: string, params: Record<string, string>): string {\n return Object.entries(params).reduce(\n (url, [key, value]) => url.replace(`{${key}}`, encodeURIComponent(value)),\n pattern,\n );\n}\n","import { Injectable } from '@angular/core';\nimport { State, Action, Selector } from '@ngxs/store';\nimport type { StateContext } from '@ngxs/store';\nimport { type ApiManifest, type ThemeConfigResponse, type ViewerApiManifest } from '../api/api-manifest.types';\n\n// --- Action ------------------------------------------------------------------\n\nexport class SetAppConfig {\n static readonly type = '[AppConfig] Set';\n constructor(public readonly config: ThemeConfigResponse) {}\n}\n\n// --- State -------------------------------------------------------------------\n\nexport interface AppConfigStateModel {\n config: ThemeConfigResponse | null;\n loaded: boolean;\n}\n\n@State<AppConfigStateModel>({\n name: 'appConfig',\n defaults: { config: null, loaded: false },\n})\n@Injectable()\nexport class AppConfigState {\n\n @Action(SetAppConfig)\n set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void {\n ctx.setState({ config, loaded: true });\n }\n\n @Selector()\n static manifest(state: AppConfigStateModel): ApiManifest | null {\n return state.config?.manifest ?? null;\n }\n\n /**\n * The viewer manifest. Null when the backend didn't ship a viewers\n * section (no tagged providers); consumers should fall back to a\n * \"no preview\" surface.\n */\n @Selector()\n static viewers(state: AppConfigStateModel): ViewerApiManifest | null {\n return state.config?.manifest.viewers ?? null;\n }\n\n @Selector()\n static loaded(state: AppConfigStateModel): boolean {\n return state.loaded;\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { type Observable } from 'rxjs';\n\nimport { AppConfigState } from '../state/app-config.state';\nimport type { ApiManifest } from './api-manifest.types';\nimport type { TokenResponse, UserDto } from './auth.types';\n\n/**\n * The identity endpoints core owns: the session lifecycle, plus the settings\n * read at boot.\n *\n * Carved out of the admin's `ApiService` -- 2410 lines, 118 methods and 89 DTOs\n * spanning calendars, documents, calls, VFS and the rest. Core cannot depend on\n * that and still ship beneath the UI kit: it would drag every module's wire\n * types down with it. Core called exactly FOUR of those methods.\n *\n * `logout` is the one addition, and not for symmetry: the auth lifecycle lives\n * here (state, refresh coordinator, cross-tab sync), so a core package that\n * could sign in but not out would be incomplete.\n *\n * `ApiService` keeps its own identical signatures and delegates here, so none\n * of the 94 feature files calling it changed.\n */\n@Injectable({ providedIn: 'root' })\nexport class IdentityApiClient {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n\n /** URLs come from the boot manifest, which core already owns. */\n private get manifest(): ApiManifest {\n const m = this.store.selectSnapshot(AppConfigState.manifest);\n if (!m) throw new Error('ApiManifest not loaded — call AppInitService.load() first');\n return m;\n }\n\n login(identifier: string, password: string): Observable<TokenResponse> {\n return this.http.post<TokenResponse>(this.manifest.auth!.login, { identifier, password });\n }\n\n refresh(refreshToken: string): Observable<TokenResponse> {\n return this.http.post<TokenResponse>(this.manifest.auth!.refresh, { refreshToken });\n }\n\n logout(): Observable<void> {\n return this.http.post<void>(this.manifest.auth!.logout, {});\n }\n\n me(): Observable<UserDto> {\n return this.http.get<UserDto>(this.manifest.auth!.me);\n }\n\n getSettings(): Observable<Record<string, Record<string, unknown>>> {\n // Force plain JSON. Angular's default Accept carries a wildcard, which\n // makes the backend prefer application/ld+json -- and the section keys\n // are then lost inside the Hydra member array.\n return this.http.get<Record<string, Record<string, unknown>>>(\n this.manifest.identity!.settingsUrl,\n { headers: { Accept: 'application/json' } },\n );\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { type Observable } from 'rxjs';\n\nimport { AppConfigState } from '../state/app-config.state';\nimport type { ApiManifest } from './api-manifest.types';\n\n/**\n * Response of `POST /centrifugo/connection-token`: the JWT the realtime SDK\n * opens its socket with, and what it needs to refresh before expiry.\n */\nexport interface CentrifugoConnectionTokenDto {\n token: string;\n expiresAt: number;\n ttl: number;\n wsUrl: string;\n /**\n * The code the server closes this connection with when the caller's session\n * ended (a sign-out everywhere, a password change, a deactivation). On it the\n * client signs out and ends its calls at once, without reconnecting or waiting\n * for a 401 (2026-09-26). Absent from a server older than that.\n */\n sessionEndedCode?: number;\n}\n\n/**\n * Response of `POST /centrifugo/subscription-token`: a per-channel JWT. The\n * SDK asks for one before subscribing to a private namespace, so this is\n * fetched per subscription rather than once per connection.\n */\nexport interface CentrifugoSubscriptionTokenDto {\n channel: string;\n token: string;\n expiresAt: number;\n ttl: number;\n}\n\n/**\n * The two realtime endpoints, carved out for the same reason the identity ones\n * were: the UI kit's realtime client needed exactly these two of `ApiService`'s\n * 117 methods, and depending on the whole thing would have pulled the entire\n * application's wire vocabulary underneath the kit.\n *\n * They belong in core rather than in the kit because both answers are derived\n * from things core already owns -- the boot manifest for the URL, and the auth\n * interceptor for the bearer token that authorises the request. A token is\n * session state, not a widget.\n *\n * `ApiService` keeps its identical signatures and delegates here, so nothing\n * calling it had to change.\n */\n@Injectable({ providedIn: 'root' })\nexport class RealtimeTokenClient {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n\n private get manifest(): ApiManifest {\n const m = this.store.selectSnapshot(AppConfigState.manifest);\n if (!m) throw new Error('ApiManifest not loaded — call AppInitService.load() first');\n return m;\n }\n\n connectionToken(): Observable<CentrifugoConnectionTokenDto> {\n return this.http.post<CentrifugoConnectionTokenDto>(\n this.manifest.apiBase + '/centrifugo/connection-token', {});\n }\n\n subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto> {\n return this.http.post<CentrifugoSubscriptionTokenDto>(\n this.manifest.apiBase + '/centrifugo/subscription-token', { channel });\n }\n}\n","import { type TokenResponse, type UserDto } from '../api/auth.types';\n\nexport class Login {\n static readonly type = '[Auth] Login';\n constructor(public identifier: string, public password: string) {}\n}\n\nexport class Logout {\n static readonly type = '[Auth] Logout';\n}\n\nexport class RestoreSession {\n static readonly type = '[Auth] Restore Session';\n}\n\nexport class SetTokens {\n static readonly type = '[Auth] Set Tokens';\n constructor(public readonly response: TokenResponse) {}\n}\n\n/** Merge a partial update into the currently-stored user (e.g. after avatar change). */\nexport class PatchCurrentUser {\n static readonly type = '[Auth] Patch Current User';\n constructor(public readonly patch: Partial<UserDto>) {}\n}\n","import { DestroyRef, inject, Injectable } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { Subject, debounceTime } from 'rxjs';\nimport { AppConfigState } from '../state/app-config.state';\nimport { type DataGridPreference } from './user-preferences.types';\n\n/**\n * Unified user preferences service -- all preference types share one\n * localStorage key (`coolms_ui_prefs`) and one debounced server-sync pipeline.\n *\n * Key naming convention (stored as nested objects, referenced via dot-notation):\n *\n * // Grids\n * grids.{gridId}.columns\n * grids.{gridId}.columnWidths\n *\n * // Panels (ExplorerLayout persistKey values from YAML)\n * panel.{persistKey}.width\n * panel.{persistKey}.collapsed\n *\n * // Page navigation state\n * page.vfs.lastPath\n * page.vfs.viewMode\n * page.media.lastDir\n * page.media.viewMode\n *\n * // Global\n * nav.lastRoute\n * terminal.height\n * terminal.open\n */\n@Injectable({ providedIn: 'root' })\nexport class UserPreferencesService {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly destroyRef = inject(DestroyRef);\n\n private readonly STORAGE_KEY = 'coolms_ui_prefs';\n private readonly SYNC_DELAY = 2000; // ms\n\n private syncSubject = new Subject<Record<string, unknown>>();\n\n constructor() {\n // Debounced sync to server -- batches rapid changes (e.g. live resize)\n this.syncSubject.pipe(\n debounceTime(this.SYNC_DELAY),\n takeUntilDestroyed(this.destroyRef),\n ).subscribe(prefs => this.syncToServer(prefs));\n }\n\n // -- Core helpers ---------------------------------------------------------\n\n /**\n * Write all prefs to localStorage and schedule a debounced server sync.\n */\n private save(all: Record<string, unknown>): void {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(all));\n this.syncSubject.next(all);\n }\n\n /**\n * Read the entire prefs object from localStorage, applying legacy key\n * migration on the first read after an upgrade.\n */\n private load(): Record<string, unknown> {\n try {\n const raw = localStorage.getItem(this.STORAGE_KEY);\n const all: Record<string, unknown> = raw ? JSON.parse(raw) : {};\n return this.migrate(all);\n } catch {\n return {};\n }\n }\n\n /**\n * One-time migration of legacy flat keys to the new nested naming convention.\n * Mutates `all` in place and re-persists only when something changed.\n */\n private migrate(all: Record<string, unknown>): Record<string, unknown> {\n let dirty = false;\n\n // terminal_height -> terminal.height\n if (typeof all['terminal_height'] === 'number') {\n const terminal = (all['terminal'] ?? {}) as Record<string, unknown>;\n terminal['height'] = all['terminal_height'];\n all['terminal'] = terminal;\n delete all['terminal_height'];\n dirty = true;\n }\n\n // media_view_mode -> page.media.viewMode\n if (typeof all['media_view_mode'] === 'string') {\n const page = (all['page'] ?? {}) as Record<string, unknown>;\n const media = (page['media'] ?? {}) as Record<string, unknown>;\n media['viewMode'] = all['media_view_mode'];\n page['media'] = media;\n all['page'] = page;\n delete all['media_view_mode'];\n dirty = true;\n }\n\n // media_collections_panel -> panel.media_panel_left (matches persistKey in media-library.yaml)\n if (all['media_collections_panel'] && typeof all['media_collections_panel'] === 'object') {\n const panel = (all['panel'] ?? {}) as Record<string, unknown>;\n panel['media_panel_left'] = all['media_collections_panel'];\n all['panel'] = panel;\n delete all['media_collections_panel'];\n dirty = true;\n }\n\n // Normalize namespace arrays to empty objects.\n // The server serialises empty namespaces as [] (JSON array) rather than {}\n // because PHP converts empty associative arrays to JSON arrays. Arrays silently\n // swallow named-property writes and produce \"[]\" on serialisation, so column/\n // panel preferences would be lost. Convert once and re-persist.\n const NAMESPACES = new Set(['grids', 'panel', 'page', 'terminal', 'nav']);\n for (const ns of NAMESPACES) {\n if (Array.isArray(all[ns])) {\n all[ns] = {};\n dirty = true;\n }\n }\n\n // Grid keys: any top-level object with `columns` or `columnWidths` -> grids.{key}\n for (const key of Object.keys(all)) {\n if (NAMESPACES.has(key)) continue;\n const val = all[key];\n if (val && typeof val === 'object' && !Array.isArray(val)) {\n const obj = val as Record<string, unknown>;\n if ('columns' in obj || 'columnWidths' in obj) {\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n grids[key] = obj;\n all['grids'] = grids;\n delete all[key];\n dirty = true;\n }\n }\n }\n\n if (dirty) {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(all));\n }\n\n return all;\n }\n\n // -- Server sync ----------------------------------------------------------\n\n /**\n * Seed localStorage from server preferences.\n * Called after login and on every session restore (page reload).\n *\n * Strategy:\n * - No local prefs yet (new device / clean install) -> use server prefs as-is.\n * - Local prefs already exist -> merge with local taking priority at each\n * top-level namespace. This ensures that locally-persisted state\n * (panel widths, last path, etc.) is never overwritten by the stale copy\n * that was saved in the auth token at login time.\n */\n loadFromServer(uiPrefs: Record<string, unknown>): void {\n if (Object.keys(uiPrefs).length === 0) return;\n try {\n const raw = localStorage.getItem(this.STORAGE_KEY);\n if (!raw) {\n // Fresh device -- trust the server entirely\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(uiPrefs));\n } else {\n // Local prefs exist -- server is the base, local namespaces win\n const local = JSON.parse(raw) as Record<string, unknown>;\n const merged = { ...uiPrefs, ...local };\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(merged));\n }\n } catch {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(uiPrefs));\n }\n }\n\n private syncToServer(prefs: Record<string, unknown>): void {\n const manifest = this.store.selectSnapshot(AppConfigState.manifest);\n const url = manifest?.auth?.preferencesUrl;\n if (!url) return;\n\n this.http.patch(url, { uiPrefs: prefs }, {\n headers: { 'Content-Type': 'application/merge-patch+json' },\n }).subscribe({\n error: err => console.warn('Preferences sync failed:', err),\n });\n }\n\n // -- Grids ----------------------------------------------------------------\n\n /**\n * Get stored preferences for a specific grid.\n */\n getGridPref(gridId: string): DataGridPreference | null {\n try {\n const all = this.load();\n const grids = all['grids'] as Record<string, unknown> | undefined;\n if (!grids) return null;\n return (grids[gridId] as DataGridPreference) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Set visible column order for a grid.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setColumns(gridId: string, columns: string[]): void {\n this.updateGrid(gridId, existing => ({ ...existing, columns }));\n }\n\n /**\n * Record a column width for a grid column.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setColumnWidth(gridId: string, field: string, width: number): void {\n this.updateGrid(gridId, existing => ({\n ...existing,\n columnWidths: {\n ...(existing.columnWidths ?? {}),\n [field]: Math.max(60, width),\n },\n }));\n }\n\n /**\n * Remove all preferences for a specific grid and immediately sync to server.\n */\n resetGrid(gridId: string): void {\n const all = this.load();\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n delete grids[gridId];\n all['grids'] = grids;\n this.save(all);\n this.syncToServer(all); // immediate sync\n }\n\n private updateGrid(\n gridId: string,\n updater: (existing: DataGridPreference) => DataGridPreference,\n ): void {\n const all = this.load();\n const grids = (all['grids'] ?? {}) as Record<string, unknown>;\n grids[gridId] = updater((grids[gridId] as DataGridPreference) ?? {});\n all['grids'] = grids;\n this.save(all);\n }\n\n // -- Terminal -------------------------------------------------------------\n\n /**\n * Get persisted terminal panel height.\n */\n getTerminalHeight(): number | null {\n const all = this.load();\n const terminal = all['terminal'] as Record<string, unknown> | undefined;\n return typeof terminal?.['height'] === 'number' ? terminal['height'] : null;\n }\n\n /**\n * Persist terminal panel height.\n */\n setTerminalHeight(height: number): void {\n const all = this.load();\n const terminal = (all['terminal'] ?? {}) as Record<string, unknown>;\n terminal['height'] = Math.round(height);\n all['terminal'] = terminal;\n this.save(all);\n }\n\n // -- Panel ----------------------------------------------------------------\n\n /**\n * Get persisted state for a resizable panel identified by key.\n * Used by ExplorerLayoutComponent to persist panel widths across sessions.\n */\n getPanelState(key: string): { width: number; collapsed: boolean } | null {\n try {\n const all = this.load();\n const panel = all['panel'] as Record<string, unknown> | undefined;\n if (!panel) return null;\n const p = panel[key];\n if (!p || typeof p !== 'object') return null;\n const { width, collapsed } = p as Record<string, unknown>;\n return typeof width === 'number' && typeof collapsed === 'boolean'\n ? { width, collapsed }\n : null;\n } catch { return null; }\n }\n\n /**\n * Persist generic panel state by key.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setPanelState(key: string, width: number, collapsed: boolean): void {\n const all = this.load();\n const panel = (all['panel'] ?? {}) as Record<string, unknown>;\n panel[key] = { width: Math.round(width), collapsed };\n all['panel'] = panel;\n this.save(all);\n }\n\n // -- Page state -----------------------------------------------------------\n\n /**\n * Get all persisted state for a page namespace (e.g. 'vfs', 'media', 'nav').\n */\n getPageState<T>(pageKey: string): T | null {\n try {\n const all = this.load();\n const page = all['page'] as Record<string, unknown> | undefined;\n if (!page) return null;\n return (page[pageKey] as T) ?? null;\n } catch { return null; }\n }\n\n /**\n * Merge partial state into a page namespace.\n * Existing keys in the namespace are preserved; only provided keys are updated.\n * Applies to localStorage instantly; server sync is debounced.\n */\n setPageState(pageKey: string, state: Record<string, unknown>): void {\n const all = this.load();\n const page = (all['page'] ?? {}) as Record<string, unknown>;\n page[pageKey] = { ...(page[pageKey] ?? {}), ...state };\n all['page'] = page;\n this.save(all);\n }\n\n // -- Media (legacy pass-through -- kept for compatibility) -----------------\n\n /** @deprecated Use getPageState('media') instead */\n getMediaViewMode(): string | null {\n return this.getPageState<{ viewMode?: string }>('media')?.viewMode ?? null;\n }\n\n /** @deprecated Use setPageState('media', ...) instead */\n setMediaViewMode(mode: string): void {\n this.setPageState('media', { viewMode: mode });\n }\n\n /** @deprecated Use getPanelState('media_panel_left') instead */\n getMediaCollectionsPanel(): { width: number; collapsed: boolean } | null {\n return this.getPanelState('media_panel_left');\n }\n\n /** @deprecated Use setPanelState('media_panel_left', ...) instead */\n setMediaCollectionsPanel(width: number, collapsed: boolean): void {\n this.setPanelState('media_panel_left', width, collapsed);\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { State, Action, Selector } from '@ngxs/store';\nimport type { StateContext } from '@ngxs/store';\nimport { catchError, of } from 'rxjs';\nimport { switchMap, tap } from 'rxjs/operators';\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport type { TokenResponse, UserDto } from '../api/auth.types';\nimport { Login, Logout, PatchCurrentUser, RestoreSession, SetTokens } from './auth.actions';\nimport { UserPreferencesService } from '../services/user-preferences.service';\n\nexport interface AuthStateModel {\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: string | null;\n user: UserDto | null;\n}\n\nconst STORAGE_KEY = 'coolms_token';\n\n@State<AuthStateModel>({\n name: 'auth',\n defaults: { accessToken: null, refreshToken: null, expiresAt: null, user: null },\n})\n@Injectable()\nexport class AuthState {\n // `inject()` rather than constructor parameters, and the imports above are\n // VALUE imports on purpose: an injected type must survive to runtime. As a\n // constructor parameter that is invisible to `tsc`, which type-checks a\n // type-only import happily and leaves the Angular compiler to fail with\n // NG2003. Read as a value here, so the same mistake is a type error.\n private readonly api = inject(IdentityApiClient);\n private readonly prefs = inject(UserPreferencesService);\n\n @Action(Login)\n login(ctx: StateContext<AuthStateModel>, { identifier, password }: Login) {\n return this.api.login(identifier, password).pipe(\n tap((res: TokenResponse) => {\n const state: AuthStateModel = {\n accessToken: res.accessToken,\n refreshToken: res.refreshToken,\n expiresAt: res.expiresAt,\n user: res.user ?? null,\n };\n ctx.setState(state);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n }),\n // Fetch full user data (includes uiPrefs) to seed preferences\n switchMap(() => this.api.me().pipe(catchError(() => of(null)))),\n tap(me => {\n if (!me) return;\n // Update stored user with full data including uiPrefs\n const updated = { ...ctx.getState(), user: me };\n ctx.setState(updated);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));\n if (me.uiPrefs) {\n this.prefs.loadFromServer(me.uiPrefs);\n }\n }),\n );\n }\n\n @Action(Logout)\n logout(ctx: StateContext<AuthStateModel>) {\n ctx.setState({ accessToken: null, refreshToken: null, expiresAt: null, user: null });\n localStorage.removeItem(STORAGE_KEY);\n }\n\n @Action(RestoreSession)\n restore(ctx: StateContext<AuthStateModel>) {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n\n try {\n const state = JSON.parse(raw) as AuthStateModel;\n // Restore whenever any token is present.\n // If only the access token has expired but the refresh token is still\n // valid, the HTTP interceptor will transparently refresh on the next\n // request. Discarding state here would throw away the refresh token\n // and force a full re-login unnecessarily.\n if (state.accessToken || state.refreshToken) {\n ctx.setState(state);\n // Seed preferences from previously stored user (covers page-reload case)\n if (state.user?.uiPrefs) {\n this.prefs.loadFromServer(state.user.uiPrefs);\n }\n } else {\n localStorage.removeItem(STORAGE_KEY);\n }\n } catch {\n localStorage.removeItem(STORAGE_KEY);\n }\n }\n\n @Action(SetTokens)\n setTokens(ctx: StateContext<AuthStateModel>, { response }: SetTokens): void {\n const state: AuthStateModel = {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n expiresAt: response.expiresAt,\n user: response.user ?? ctx.getState().user,\n };\n ctx.setState(state);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n }\n\n @Action(PatchCurrentUser)\n patchCurrentUser(ctx: StateContext<AuthStateModel>, { patch }: PatchCurrentUser): void {\n const current = ctx.getState();\n if (!current.user) return;\n const updated = { ...current, user: { ...current.user, ...patch } };\n ctx.setState(updated);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));\n }\n\n @Selector()\n static isAuthenticated(state: AuthStateModel): boolean {\n return null !== state.accessToken;\n }\n\n @Selector()\n static accessToken(state: AuthStateModel): string | null {\n return state.accessToken;\n }\n\n @Selector()\n static refreshToken(state: AuthStateModel): string | null {\n return state.refreshToken;\n }\n\n /**\n * Sub-phase Y -- expose the access-token expiry timestamp so\n * preemptive-refresh callers (e.g., Centrifuge getToken\n * callbacks) can decide whether to rotate before issuing the\n * next protected request, instead of relying on the\n * interceptor's reactive 401-retry path.\n */\n @Selector()\n static expiresAt(state: AuthStateModel): string | null {\n return state.expiresAt;\n }\n\n @Selector()\n static currentUser(state: AuthStateModel): UserDto | null {\n return state.user;\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { type Observable, catchError, defer, finalize, firstValueFrom, from, shareReplay, throwError } from 'rxjs';\n\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport { AuthState } from './auth.state';\nimport { Logout, SetTokens } from './auth.actions';\n\nconst LOCK_NAME = 'coolms_auth_refresh';\nconst FRESH_TOKEN_GRACE_MS = 30_000;\n\n/** Where AuthState persists the session, shared by every tab of the origin. */\nconst STORAGE_KEY = 'coolms_token';\n\ninterface SharedTokens {\n accessToken: string;\n refreshToken: string;\n expiresAt: string;\n}\n\n/**\n * Two-layer refresh coordinator: an intra-tab `shareReplay(1)`\n * mutex dedupes concurrent callers within one tab, and a\n * cross-tab `navigator.locks` wrapper ensures only one tab in\n * the browser issues the actual `POST /auth/refresh` at a time.\n *\n * Without the cross-tab layer, two tabs racing the same expired\n * refresh-token both POST `/auth/refresh`; the backend's\n * `isUsed` replay branch revokes every token for the user on\n * the second arrival and the operator is kicked to /login.\n *\n * The Web Lock body re-reads the current access-token expiry from\n * `AuthState` before issuing the HTTP call. When this tab waited\n * behind another tab's refresh, that tab's `SetTokens` dispatch\n * has already published fresh values into shared `localStorage`,\n * `CrossTabAuthSyncService` has mirrored them into this tab's\n * NGXS state, and the lock body simply returns the cached access\n * token without another network round-trip.\n *\n * `Logout` fires only when the refresh endpoint returns 401\n * (real auth failure). Transient errors (network blips, 5xx,\n * CORS, timeouts) propagate without clearing the session so a\n * momentary refresh failure does not kill a valid login.\n *\n * A token the server REFUSED (2026-09-26). A caller answering a 401\n * passes the access token that was refused. Until then the lock body\n * handed back the cached token whenever its expiry looked fresh --\n * which a revoked token's does: a session ended on the server (a\n * sign-out everywhere from another device, a password change) was\n * retried with the same dead token and never refreshed, so the client\n * stayed signed in, and in its calls, until the token neared expiry\n * (measured: a 1:1 call kept for the whole 30 s window, every request\n * answered 401). Now the lock body re-reads the SHARED copy\n * (`localStorage`, which every tab's `SetTokens` writes synchronously;\n * this tab's state only follows a sibling's write when its `storage`\n * event arrives):\n * - a different access token there -> another tab (or an earlier\n * refresh here) already rotated past the refused one: use it, no\n * request;\n * - the same one -> the server refused this token: refresh, with the\n * newest refresh token -- the shared one, so a rotation a sibling\n * made is never replayed with the token it consumed. A 401 there\n * is the session's end: `Logout`.\n */\n@Injectable({ providedIn: 'root' })\nexport class AuthRefreshCoordinator {\n private readonly api = inject(IdentityApiClient);\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n\n private inFlight: Observable<string> | null = null;\n\n /**\n * Return the in-flight refresh Observable, or spawn a new one\n * with the supplied refresh-token value.\n *\n * `refusedAccessToken`: the access token the server just answered\n * 401 to, when the caller is answering one. With it, the lock body\n * refreshes unless the shared copy already holds another token;\n * without it (a preemptive refresh), a fresh-looking cached token\n * is handed back as before.\n *\n * Callers subscribe via `switchMap` / `firstValueFrom` to retry\n * their original request with the resolved access token.\n */\n refresh(refreshToken: string, refusedAccessToken: string | null = null): Observable<string> {\n if (this.inFlight) {\n return this.inFlight;\n }\n this.inFlight = defer(() => this.refreshWithCrossTabLock(refreshToken, refusedAccessToken)).pipe(\n catchError(refreshErr => {\n const isAuthFailure = refreshErr instanceof HttpErrorResponse && refreshErr.status === 401;\n if (isAuthFailure) {\n this.store.dispatch(new Logout());\n if (this.router.navigated) {\n void this.router.navigate(['/login']);\n }\n }\n return throwError(() => refreshErr);\n }),\n shareReplay(1),\n finalize(() => {\n this.inFlight = null;\n }),\n );\n return this.inFlight;\n }\n\n /**\n * Run the refresh body inside `navigator.locks` when the API is\n * available; degrade to direct invocation otherwise. The lock\n * body checks for state already updated by a sibling tab before\n * issuing its own HTTP call.\n */\n private refreshWithCrossTabLock(refreshToken: string, refusedAccessToken: string | null): Observable<string> {\n const lockBody = (): Promise<string> => this.executeRefresh(refreshToken, refusedAccessToken);\n\n if (typeof navigator === 'undefined' || !('locks' in navigator)) {\n return from(lockBody());\n }\n return from(\n navigator.locks.request(LOCK_NAME, lockBody).then(value => {\n if (value === undefined) {\n throw new Error('Refresh lock aborted before completing.');\n }\n return value;\n }),\n );\n }\n\n /**\n * Lock body. Re-reads the shared copy first (see the class note).\n *\n * Answering a refusal: a shared access token other than the refused\n * one is a rotation already made -- taken, and mirrored into this\n * tab's state if its `storage` event has not arrived yet. Otherwise,\n * or for a preemptive caller whose cached token no longer looks\n * fresh, the HTTP refresh, with the newest refresh token: shared,\n * then state, then the caller's.\n */\n private async executeRefresh(originalRefreshToken: string, refusedAccessToken: string | null): Promise<string> {\n const shared = this.readShared();\n if (refusedAccessToken !== null) {\n if (shared !== null && shared.accessToken !== refusedAccessToken) {\n if (this.store.selectSnapshot(AuthState.accessToken) !== shared.accessToken) {\n this.store.dispatch(new SetTokens(shared));\n }\n return shared.accessToken;\n }\n } else {\n const cachedAccess = this.tryConsumeFreshState(shared);\n if (cachedAccess !== null) {\n return cachedAccess;\n }\n }\n const currentRefreshToken = shared?.refreshToken\n ?? this.store.selectSnapshot(AuthState.refreshToken)\n ?? originalRefreshToken;\n const response = await firstValueFrom(this.api.refresh(currentRefreshToken));\n this.store.dispatch(new SetTokens(response));\n return response.accessToken;\n }\n\n /**\n * The session as the tabs share it, or null when there is none to\n * read: no storage, nothing stored (signed out), or a value that is\n * not a complete token set.\n */\n private readShared(): SharedTokens | null {\n let raw: string | null;\n try {\n raw = localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n if (raw === null) {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as Partial<SharedTokens> | null;\n if (typeof parsed?.accessToken !== 'string' || typeof parsed.refreshToken !== 'string' || typeof parsed.expiresAt !== 'string') {\n return null;\n }\n return { accessToken: parsed.accessToken, refreshToken: parsed.refreshToken, expiresAt: parsed.expiresAt };\n } catch {\n return null;\n }\n }\n\n /**\n * For a preemptive caller only: return the current access token when\n * its expiry is more than `FRESH_TOKEN_GRACE_MS` milliseconds away --\n * signalling that some other tab refreshed for us. The shared copy\n * first, which a sibling's rotation reaches before this tab's state\n * does (mirrored here when it is newer); this tab's state when there\n * is none. Null otherwise. Never asked for a token the server\n * refused: a refused token's expiry looks just as fresh.\n */\n private tryConsumeFreshState(shared: SharedTokens | null): string | null {\n const accessToken = shared?.accessToken ?? this.store.selectSnapshot(AuthState.accessToken);\n const expiresAtStr = shared?.expiresAt ?? this.store.selectSnapshot(AuthState.expiresAt);\n if (accessToken === null || expiresAtStr === null) {\n return null;\n }\n const expiresAtMs = new Date(expiresAtStr).getTime();\n if (Number.isNaN(expiresAtMs)) {\n return null;\n }\n if (Date.now() >= expiresAtMs - FRESH_TOKEN_GRACE_MS) {\n return null;\n }\n if (shared !== null && this.store.selectSnapshot(AuthState.accessToken) !== shared.accessToken) {\n this.store.dispatch(new SetTokens(shared));\n }\n return accessToken;\n }\n}\n","import { inject } from '@angular/core';\nimport {\n HttpContextToken, type HttpInterceptorFn,\n HttpErrorResponse,\n} from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { Router } from '@angular/router';\nimport { AuthState } from './auth.state';\nimport { Logout } from './auth.actions';\nimport { AuthRefreshCoordinator } from './auth-refresh.coordinator';\nimport { AppInitService } from '../bootstrap/app-init.service';\nimport { catchError, switchMap, take, throwError } from 'rxjs';\n\n// -- Opt-out context token -----------------------------------------------------\n//\n// Set BYPASS_AUTH to true on any HttpRequest to skip adding the Authorization\n// header entirely. Used by AppInitService for the bootstrap config request so\n// that a stale localStorage token does not cause a 401 on a public endpoint.\n\nexport const BYPASS_AUTH = new HttpContextToken<boolean>(() => false);\n\n// -----------------------------------------------------------------------------\n\nexport const authInterceptor: HttpInterceptorFn = (req, next) => {\n const store = inject(Store);\n const router = inject(Router);\n const coordinator = inject(AuthRefreshCoordinator);\n const initService = inject(AppInitService);\n\n // Caller explicitly requested no auth header (e.g. the bootstrap config\n // fetch -- the endpoint is public and a stale token would cause 401).\n if (req.context.get(BYPASS_AUTH)) {\n return next(req);\n }\n\n // Auth endpoints are public by design. The backend's JWT firewall rejects any\n // request that carries an invalid/expired Bearer token -- even on endpoints\n // that would otherwise allow anonymous access -- so never forward a stored\n // token to login or refresh.\n if (req.url.includes('/auth/login') || req.url.includes('/auth/refresh')) {\n return next(req);\n }\n\n // Queue behind AppInitService.ready$ so that token restoration from\n // localStorage (RestoreSession) is guaranteed to be in NGXS state before\n // we read it. On F5 the interceptor fires synchronously for every pending\n // request before APP_INITIALIZER has completed; without this guard the\n // token snapshot is null and every request gets a 401 -> refresh cycle even\n // for a perfectly valid session.\n //\n // ready$ is a ReplaySubject(1) that completes after load() finishes, so:\n // - requests fired during init queue here and flush the moment ready$ emits;\n // - requests fired after init pass through take(1) immediately (replayed value).\n return initService.ready$.pipe(\n take(1),\n switchMap(() => {\n const token = store.selectSnapshot(AuthState.accessToken);\n const authed = token\n ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })\n : req;\n\n return next(authed).pipe(\n catchError((err: unknown) => {\n if (!(err instanceof HttpErrorResponse) || err.status !== 401) {\n return throwError(() => err);\n }\n\n // Avoid infinite loop -- skip refresh for auth endpoints themselves.\n if (req.url.includes('/auth/refresh') || req.url.includes('/auth/login')) {\n return throwError(() => err);\n }\n\n const refreshToken = store.selectSnapshot(AuthState.refreshToken);\n if (!refreshToken) {\n store.dispatch(new Logout());\n // Only navigate programmatically once the router has already\n // completed its initial navigation. During APP_INITIALIZER the\n // router is still blocked; authGuard handles the redirect there.\n if (router.navigated) void router.navigate(['/login']);\n return throwError(() => err);\n }\n\n // Delegate refresh to the shared coordinator so the\n // Centrifuge getToken path and the interceptor share ONE\n // in-flight refresh. Without this, two parallel\n // `api.refresh(refreshToken)` calls (one from each mutex)\n // would send the same refresh-token twice and trigger the\n // backend's `isUsed` replay branch on the second arrival,\n // wiping the whole session.\n //\n // With the token that was refused, so the coordinator\n // refreshes rather than hand the same dead token back\n // because its expiry still looks fresh (a session ended\n // on the server went unnoticed until then, 2026-09-26).\n return coordinator.refresh(refreshToken, token).pipe(\n switchMap(newToken =>\n next(req.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } })),\n ),\n );\n }),\n );\n }),\n );\n};\n","import { Injectable, inject } from '@angular/core';\nimport { Store } from '@ngxs/store';\n\nimport type { AuthStateModel } from './auth.state';\nimport { Logout, SetTokens } from './auth.actions';\nimport type { TokenResponse } from '../api/auth.types';\n\nconst STORAGE_KEY = 'coolms_token';\n\n/**\n * Bridges shared `localStorage` mutations into the NGXS auth\n * state so a refresh executed in one tab silently propagates to\n * every other open tab.\n *\n * The browser fires `storage` events in tabs OTHER than the one\n * that mutated `localStorage`. So when Tab A's\n * `AuthRefreshCoordinator` rotates the token pair and `SetTokens`\n * persists the new values, every sibling tab receives an event\n * here and dispatches `SetTokens` against its own NGXS state --\n * no extra HTTP roundtrip, no extra refresh contention.\n *\n * Pairs with the `navigator.locks` cross-tab mutex in\n * `AuthRefreshCoordinator`: locks ensure only one tab issues the\n * refresh, this listener propagates the result to the rest.\n *\n * `Logout` from any tab clears `coolms_token` (newValue === null)\n * and this listener mirrors that into the sibling tabs so the\n * shell redirects to `/login` without waiting for the next\n * protected request to 401.\n *\n * Started from `AppInitService.load()` so the listener is in\n * place before any protected HTTP fires.\n */\n@Injectable({ providedIn: 'root' })\nexport class CrossTabAuthSyncService {\n private readonly store = inject(Store);\n private listenerAttached = false;\n\n start(): void {\n if (this.listenerAttached) {\n return;\n }\n this.listenerAttached = true;\n window.addEventListener('storage', this.handleStorageEvent);\n }\n\n private readonly handleStorageEvent = (event: StorageEvent): void => {\n if (event.key !== STORAGE_KEY) {\n return;\n }\n // Sibling tab cleared the key (logout dispatch or\n // RestoreSession parse-error cleanup). Mirror the clear so\n // this tab does not keep operating against a revoked\n // server-side session.\n if (event.newValue === null) {\n this.store.dispatch(new Logout());\n return;\n }\n // Parse the new value and dispatch SetTokens so this tab's\n // in-memory NGXS state reflects what the originating tab\n // just wrote. Malformed payloads are ignored.\n let parsed: AuthStateModel;\n try {\n parsed = JSON.parse(event.newValue) as AuthStateModel;\n } catch {\n return;\n }\n if (typeof parsed.accessToken !== 'string' || typeof parsed.refreshToken !== 'string' || typeof parsed.expiresAt !== 'string') {\n return;\n }\n const tokenResponse: TokenResponse = {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: parsed.expiresAt,\n user: parsed.user ?? undefined,\n };\n this.store.dispatch(new SetTokens(tokenResponse));\n };\n}\n","import { Injectable, inject } from '@angular/core';\nimport { Location } from '@angular/common';\nimport { HttpClient, HttpContext } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { catchError, firstValueFrom, map, of, ReplaySubject, tap } from 'rxjs';\nimport { Logout, RestoreSession } from '../auth/auth.actions';\nimport { AuthState } from '../auth/auth.state';\nimport { BYPASS_AUTH } from '../auth/auth.interceptor';\nimport { AuthRefreshCoordinator } from '../auth/auth-refresh.coordinator';\nimport { CrossTabAuthSyncService } from '../auth/cross-tab-auth-sync.service';\nimport { SetAppConfig } from '../state/app-config.state';\nimport { type ThemeConfigResponse } from '../api/api-manifest.types';\nimport { UserPreferencesService } from '../services/user-preferences.service';\n\n@Injectable({ providedIn: 'root' })\nexport class AppInitService {\n // Injected as values, not constructor parameters: an injection token has to\n // exist at runtime, and a type-only import of one type-checks clean while\n // the Angular compiler rejects it with NG2003.\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n private readonly location = inject(Location);\n private readonly prefs = inject(UserPreferencesService);\n private readonly crossTabSync = inject(CrossTabAuthSyncService);\n private readonly refreshCoordinator = inject(AuthRefreshCoordinator);\n\n /** The only hardcoded URL -- everything else comes from the manifest. */\n private readonly configUrl = '/api/v1/theme/config';\n\n /**\n * Emits (once) when load() has fully completed -- tokens settled, manifest\n * set (or swallowed on error). authGuard subscribes to this so it never\n * evaluates isAuthenticated against a partially-restored stale token.\n */\n private readonly initComplete = new ReplaySubject<void>(1);\n readonly ready$ = this.initComplete.asObservable();\n\n async load(): Promise<void> {\n // -- Step 0: cross-tab sync --------------------------------------------\n //\n // Install the storage-event listener before RestoreSession so that\n // any localStorage mutation from a sibling tab during bootstrap\n // (e.g., a parallel tab refreshing tokens just as we load) is mirrored\n // into this tab's NGXS state.\n this.crossTabSync.start();\n\n // -- Step 1: restore tokens from localStorage --------------------------\n //\n // RestoreSession is synchronous (ctx.setState from localStorage), so\n // this await resolves in the same microtask. Tokens are in NGXS state\n // before ANY subsequent HTTP request fires -- the auth interceptor reads\n // the token from state when attaching Authorization headers, so this\n // ordering prevents unauthenticated requests on page reload (F5).\n await firstValueFrom(this.store.dispatch(new RestoreSession()));\n\n // -- Step 2: fetch the manifest ----------------------------------------\n //\n // BYPASS_AUTH is required here -- not optional. The manifest endpoint\n // is public, but two specific failure modes make bypassing necessary:\n //\n // a) The stored access token may be expired. Without BYPASS_AUTH the\n // interceptor attaches the expired token, gets a 401, and tries to\n // refresh. But the refresh endpoint URL comes from the manifest\n // (api.refresh() reads manifest.auth.refresh), which hasn't been\n // loaded yet -- triggering a null-dereference crash before the app\n // can recover.\n //\n // b) Even if the token is valid, attaching it to a public endpoint\n // that ignores auth adds unnecessary coupling.\n //\n // Consequence: every page load issues one unauthenticated config\n // request, which is intentional. All subsequent API calls carry the\n // restored (or refreshed) token normally.\n const config = await firstValueFrom(\n this.http.get<ThemeConfigResponse>(this.configUrl, {\n context: new HttpContext().set(BYPASS_AUTH, true),\n }).pipe(\n tap(cfg => this.store.dispatch(new SetAppConfig(cfg))),\n catchError(() => {\n console.warn('CoolMS: theme/config unreachable, using safe defaults');\n return of(null);\n }),\n ),\n );\n\n // -- Step 3: validate the restored session -----------------------------\n //\n // RestoreSession trusts whatever tokens are in localStorage, but they may\n // have been revoked out-of-band -- most notably, signing out on the public\n // SSR site revokes the user's WHOLE token set. We MUST settle the real\n // auth state here, BEFORE step 4 signals ready$: both the auth interceptor\n // AND authGuard gate on ready$, so if we signalled first, the guard would\n // race ahead and mount the shell against the stale isAuthenticated=true\n // before this probe finished (-> half-loaded shell + \"Session expired\"\n // toasts, the exact bug this fixes). Because the probe itself cannot wait\n // on ready$ (that would deadlock), validateSession() bypasses the\n // interceptor and attaches the token / drives the refresh by hand.\n const meUrl = config?.manifest.identity?.meUrl;\n if (meUrl && this.store.selectSnapshot(AuthState.isAuthenticated)) {\n await this.validateSession(meUrl);\n }\n\n // -- Step 4: signal authGuard + unblock the interceptor ----------------\n //\n // Auth state is now settled (session confirmed, refreshed, or Logout\n // dispatched). ReplaySubject(1) replays to guards/requests that subscribe\n // afterwards, so they evaluate against the final isAuthenticated value.\n this.initComplete.next();\n this.initComplete.complete();\n\n // -- Step 5: navigate to last-visited route ----------------------------\n //\n // Only when the session survived validation. If it did not, isAuthenticated\n // is now false, we skip the saved-route navigation, and authGuard sends the\n // initial navigation to /login.\n //\n // Only restore when the user opened the bare /admin root. An explicit\n // deep link (pasted /admin/sync-fleet, a bookmark, a shared URL) must\n // win over the saved route. The router cannot tell us which one this is:\n // withEnabledBlockingInitialNavigation holds the initial navigation until\n // this initializer resolves, so router.url is still '/'. The browser\n // location is the only record of what was requested -- Location.path()\n // reads it with the /admin base href stripped, so the bare root (with or\n // without trailing slash) yields ''. Anything else, including a root URL\n // carrying query params, is treated as an explicit destination.\n if (this.store.selectSnapshot(AuthState.isAuthenticated)) {\n const requested = this.location.path();\n if (requested === '' || requested === '/') {\n const saved = this.prefs.getPageState<{ lastRoute?: string }>('nav');\n if (saved?.lastRoute && !saved.lastRoute.startsWith('/login')) {\n void this.router.navigateByUrl(saved.lastRoute);\n }\n }\n }\n }\n\n /**\n * Confirm the restored session is still alive, settling AuthState before the\n * app unblocks. Runs OUTSIDE the auth interceptor (BYPASS_AUTH) because the\n * interceptor queues behind ready$, which has not fired yet at this point.\n *\n * - access token accepted -> done, session alive;\n * - access token rejected -> attempt ONE refresh (access tokens are short-lived,\n * so an expired one with a valid refresh token must\n * stay logged in). The coordinator dispatches Logout\n * on a refresh 401 and SetTokens on success; a\n * transient failure (5xx/network) is swallowed so a\n * momentary blip never kills a valid session.\n */\n private async validateSession(meUrl: string): Promise<void> {\n const token = this.store.selectSnapshot(AuthState.accessToken);\n if (await this.probe(meUrl, token)) {\n return;\n }\n const refreshToken = this.store.selectSnapshot(AuthState.refreshToken);\n if (!refreshToken) {\n this.store.dispatch(new Logout());\n return;\n }\n let refreshedToken: string | null = null;\n try {\n refreshedToken = await firstValueFrom(this.refreshCoordinator.refresh(refreshToken, token));\n } catch {\n // 401 -> coordinator already dispatched Logout; transient -> session kept.\n return;\n }\n // Refresh resolved. Handed the refused token, the coordinator no longer\n // returns that same token (2026-09-26) -- it did when its stored expiry\n // looked fresh, which a revoked token's does. The re-probe stays: a\n // second rejection means the session is genuinely gone, whatever path\n // the coordinator took.\n if (!(await this.probe(meUrl, refreshedToken))) {\n this.store.dispatch(new Logout());\n }\n }\n\n /** GET an authed endpoint with an explicit Bearer header, bypassing the interceptor. */\n private probe(url: string, token: string | null): Promise<boolean> {\n return firstValueFrom(\n this.http.get(url, {\n context: new HttpContext().set(BYPASS_AUTH, true),\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n }).pipe(\n map(() => true),\n catchError(() => of(false)),\n ),\n );\n }\n}\n","import { inject } from '@angular/core';\nimport { type CanActivateFn, Router } from '@angular/router';\nimport { map, take } from 'rxjs';\nimport { Store } from '@ngxs/store';\nimport { AuthState } from './auth.state';\nimport { AppInitService } from '../bootstrap/app-init.service';\n\n/**\n * Protects routes that require authentication.\n *\n * The guard is intentionally ASYNC: it waits for AppInitService.ready$ before\n * evaluating auth state. Without this, Angular's router can evaluate\n * isAuthenticated while APP_INITIALIZER is still in-flight -- RestoreSession\n * will have placed a non-null (but expired) token in state, causing the guard\n * to pass even though the subsequent config/refresh requests will fail and\n * dispatch Logout. Waiting for ready$ guarantees the auth state is settled.\n */\nexport const authGuard: CanActivateFn = () => {\n const init = inject(AppInitService);\n const store = inject(Store);\n const router = inject(Router);\n\n return init.ready$.pipe(\n take(1),\n map(() => {\n if (store.selectSnapshot(AuthState.isAuthenticated)) {\n return true;\n }\n return router.createUrlTree(['/login']);\n }),\n );\n};\n","import { inject } from '@angular/core';\nimport { type CanActivateFn, Router } from '@angular/router';\nimport { map, take } from 'rxjs';\nimport { Store } from '@ngxs/store';\nimport { AuthState } from './auth.state';\nimport { AppInitService } from '../bootstrap/app-init.service';\n\n/**\n * Guards the `/login` route -- redirects already-authenticated\n * callers to admin home so a stale tab parked on `/login` cannot\n * remain mounted while another tab is logged in. When Tab A logs\n * in fresh, Tab B's `RestoreSession` (driven by APP_INITIALIZER)\n * reads the new token from shared `localStorage` and populates\n * `AuthState`; this guard then sees `isAuthenticated === true`\n * and routes Tab B to `/`.\n *\n * Same `ready$` wait as `authGuard` -- ensures `RestoreSession`\n * has run before the snapshot is read; otherwise a fresh F5 on\n * `/login` with a valid stored session would briefly evaluate\n * as anonymous, allow `/login`, and only then have the token\n * populated.\n */\nexport const loginPageGuard: CanActivateFn = () => {\n const init = inject(AppInitService);\n const store = inject(Store);\n const router = inject(Router);\n\n return init.ready$.pipe(\n take(1),\n map(() => {\n if (store.selectSnapshot(AuthState.isAuthenticated)) {\n return router.createUrlTree(['/']);\n }\n return true;\n }),\n );\n};\n","/**\n * The elevation state of the signed-in session: the wire shape of\n * `GET /auth/elevation`, returned again by `POST` on a grant.\n *\n * Elevation is state, not a role. It lasts a fixed time from the grant, ends\n * when the admin panel is closed or reloaded, and the client derives nothing\n * about it from mode bits or membership: this record and the node resource's\n * capability flags are the two readings of ONE server-side decision, and the\n * client keeps no third.\n */\nexport interface ElevationState {\n /** Whether this session is elevated right now. */\n elevated: boolean;\n /** When the current elevation ends (ISO 8601); absent or null when not elevated. */\n expiresAt?: string | null;\n /** When the current elevation was granted (ISO 8601); absent or null when not elevated. */\n grantedAt?: string | null;\n /** How the LAST elevation of this session ended. Rendered by the prompt before it asks. */\n ended: ElevationEnded;\n /** The lifetime a grant made now would get, in seconds (the setting, clamped to the ceiling). */\n lifetimeSeconds: number;\n /** The most any elevation can last, fixed in code. */\n lifetimeCeilingSeconds: number;\n /** Whether the CURRENT elevation was confirmed by a second factor; absent or null when not elevated. */\n factorConfirmed?: boolean | null;\n /** Whether a grant made now must carry a code from the admin authenticator. */\n mfaRequired: boolean;\n /** What the prompt shows before asking; two of the codes mean the installation cannot elevate yet. */\n warnings: ElevationWarning[];\n}\n\n/**\n * `closed` is the beacon (the panel was closed or reloaded), `dropped_by_user`\n * the explicit drop, `refresh_from_other_pair` the server's refresh tripwire,\n * `never` a session that has not elevated yet.\n */\nexport type ElevationEndedReason =\n | 'expired'\n | 'closed'\n | 'dropped_by_user'\n | 'refresh_from_other_pair'\n | 'never';\n\nexport interface ElevationEnded {\n reason: ElevationEndedReason;\n /** ISO 8601, null for `never`. */\n at: string | null;\n}\n\n/**\n * `elevation.password_not_set` and `elevation.factor_missing` mean the\n * installation cannot elevate and the message names the server command;\n * `elevation.mfa_off` is the recommendation carried while the switch is off.\n */\nexport interface ElevationWarning {\n /** The three the server documents today; the client renders any it does not know as text. */\n code: string;\n message: string;\n}\n\nexport const ELEVATION_WARNING_PASSWORD_NOT_SET = 'elevation.password_not_set';\nexport const ELEVATION_WARNING_FACTOR_MISSING = 'elevation.factor_missing';\nexport const ELEVATION_WARNING_MFA_OFF = 'elevation.mfa_off';\n\n/** Why the elevation of a session changed, for whoever refetches on it. */\nexport type ElevationChangeKind = 'granted' | 'dropped' | 'expired' | 'observed';\n\nexport interface ElevationChange {\n kind: ElevationChangeKind;\n state: ElevationState;\n}\n\n/** The two reasons the client sends when it drops an elevation itself. */\nexport type ElevationDropReason = 'dropped_by_user' | 'closed';\n","import { InjectionToken } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport type { ElevationState } from './elevation.types';\n\n/**\n * What the elevation prompt is told when it opens.\n *\n * `state` is the server's answer at the moment of asking -- `ended.reason` is\n * rendered first (a person asked for a password again after F5\n * with no explanation reads it as a fault), `warnings` may say the\n * installation cannot elevate yet, `mfaRequired` decides whether a code field\n * shows from the start.\n *\n * `refusal` is the server's own sentence for the action that was refused\n * (\"Permission denied: cannot write '/docs/...'\"), when a 403 opened the\n * prompt; absent when a disabled control offered elevation instead.\n */\nexport interface ElevationPromptRequest {\n state: ElevationState;\n refusal?: string;\n}\n\n/**\n * The prompt itself is a dialog, and dialogs are the kit's -- a layer above\n * this one. Core owns the state, the interceptor and the decision to ask; the\n * application binds this port to whatever renders the question. Same shape as\n * `CURRENT_SECTION`: core declares the little it needs, the composition root\n * provides it.\n *\n * Resolves to true when the session is elevated by the time the dialog closes,\n * false when the person declined or could not. The port does not retry the\n * refused action; the interceptor does, on true.\n */\nexport interface ElevationPromptPort {\n open(request: ElevationPromptRequest): Observable<boolean>;\n}\n\n/**\n * Optional by design: with nothing bound, a 403 stays a 403 and the state\n * service still answers -- an application without an admin panel has no\n * prompt to show and loses nothing.\n */\nexport const ELEVATION_PROMPT = new InjectionToken<ElevationPromptPort>(\n 'coolms.elevation-prompt',\n);\n","import { DestroyRef, inject, Injectable, signal, untracked } from '@angular/core';\nimport { HttpClient, HttpContext, HttpContextToken } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { defer, finalize, type Observable, of, shareReplay, Subject, switchMap, tap } from 'rxjs';\nimport { AppConfigState } from '../state/app-config.state';\nimport { ELEVATION_PROMPT, type ElevationPromptRequest } from './elevation-prompt.port';\nimport type {\n ElevationChange, ElevationChangeKind, ElevationDropReason, ElevationState,\n} from './elevation.types';\n\n/**\n * Set on a request that must never open the elevation prompt: the elevation\n * endpoint's own calls (a wrong password is a 403 and is not a refused action),\n * and any caller that would rather receive its 403 and decide for itself.\n */\nexport const BYPASS_ELEVATION = new HttpContextToken<boolean>(() => false);\n\n/**\n * The `storage` key whose write announces \"this session's elevation changed\"\n * to the other tabs of the same browser. One token chain is shared across\n * tabs, so a grant or a drop in one tab is true in all of\n * them; the others learn of it here rather than at their next 403.\n */\nconst CROSS_TAB_KEY = 'coolms.elevation.changed';\n\n/**\n * The shortest gap between two reconciling reads. `visibilitychange` and\n * `focus` both fire when a window is re-activated, so without this a single\n * alt-tab costs two identical GETs.\n */\nconst RECONCILE_MIN_INTERVAL_MS = 2_000;\n\n/**\n * The elevation state of this session, read from ONE place: the server.\n *\n * The elevation client requirement, in one service. The state endpoint is the only\n * source -- `elevated`, `expiresAt`, how the last elevation ended -- and every\n * change of it is announced on `changes$` so the Explorer listing can refetch\n * (its capability flags are the server's reading of the same state, computed\n * when the listing was fetched, and a `write: true` fetched while elevated\n * must not outlive the elevation).\n *\n * **Expiry is driven by the READ, not by a timer.** An elevation ends because\n * time passed, and time is not an event; a client that waits to be told has\n * nothing to wait for when the machine was asleep at the moment. So `state()`\n * does the arithmetic every time it is asked, and an answer whose `expiresAt`\n * has gone by reports expired without a request. The timer below still fires\n * at `expiresAt` and the tab still re-asks when it comes back into view -- both\n * are how the SERVER's answer is refreshed, which is a different thing from\n * knowing this one has run out.\n *\n * This is not a second decider. The server said \"elevated until T\"; after T,\n * continuing to report `elevated: true` asserts something it never said.\n *\n * The prompt is not here: dialogs are the kit's, a layer above core. The\n * service asks the `ELEVATION_PROMPT` port and shares ONE in-flight prompt, so\n * a burst of refused requests (a multi-select delete is one 403 per node)\n * opens one dialog and every caller waits on the same answer.\n */\n/**\n * The header a 403 refused for want of elevation carries, with the gate's name\n * as its value. The server stamps it where the gate refused; the interceptor\n * prompts on it and on nothing else. There is no list of gated URLs on this\n * side any more: the one that stood here lagged the server by every route\n * whose refusal came from the VFS permission service under another module's\n * path (media, document and word templates, content distribution).\n */\nexport const ELEVATION_REQUIRED_HEADER = 'X-Elevation-Required';\n\n@Injectable({ providedIn: 'root' })\nexport class ElevationService {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly prompt = inject(ELEVATION_PROMPT, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n /** The last answer the server gave, exactly as it gave it. */\n private readonly served = signal<ElevationState | null>(null);\n\n /**\n * The state as it stands NOW; null until the first read from the server.\n *\n * Deliberately NOT a `computed`. A cached derivation is only recomputed\n * when one of its dependencies changes, and nothing changes when an\n * elevation simply runs out -- so it would keep answering `elevated: true`\n * past the moment the server itself named, for as long as the machine\n * happened to be asleep.\n *\n * Reading past `expiresAt` also demotes the STORED answer. The value\n * returned here is already correct without that write; the write is only\n * how cached readers (a `computed`, an OnPush template) find out.\n */\n readonly state = (): ElevationState | null => {\n const served = this.served();\n if (served === null || !isPast(served)) return served;\n\n const expired = expiredView(served);\n // untracked: this read can happen inside a computed or a template, and\n // a signal write is refused there. The demotion is not a state change\n // of its own -- it is the answer we already hold, read correctly.\n untracked(() => this.served.set(expired));\n this.announceExpiryLater();\n return expired;\n };\n\n /** Convenience over `state`: false until the server has said otherwise. */\n readonly elevated = (): boolean => this.state()?.elevated ?? false;\n\n /** `expiresAt` as a Date, null when not elevated. */\n readonly expiresAt = (): Date | null => {\n const iso = this.state()?.expiresAt;\n return iso ? new Date(iso) : null;\n };\n\n /**\n * Every change of elevation state: a grant, a drop (by this person, by the\n * beacon, by the tripwire), an expiry, or an observed change of a live\n * grant's `expiresAt`. A refresh that reports the same state emits nothing,\n * so subscribers that refetch a listing on it never refetch for no reason.\n */\n readonly changes$ = new Subject<ElevationChange>();\n\n private expiryTimer: ReturnType<typeof setTimeout> | null = null;\n private pendingPrompt: Observable<boolean> | null = null;\n private expiryAnnouncePending = false;\n private lastReconcileAt = 0;\n\n constructor() {\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', this.onStorage);\n window.addEventListener('focus', this.onFocus);\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n }\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('storage', this.onStorage);\n window.removeEventListener('focus', this.onFocus);\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n }\n this.clearTimer();\n });\n }\n }\n\n /** The manifest carries the URL; without it the service is inert, not broken. */\n get available(): boolean {\n return this.url !== null;\n }\n\n private get url(): string | null {\n return this.store.selectSnapshot(AppConfigState.manifest)?.identity?.elevationUrl ?? null;\n }\n\n /**\n * Whether a URL belongs to THIS API: a relative one under the manifest's\n * `apiBase`, or an absolute one on this origin under it. The interceptor\n * acts on a stamped refusal from here and from nowhere else -- a third\n * party answering 403 with a header of the same name is not an invitation\n * to elevate this session.\n */\n isThisApi(url: string): boolean {\n const apiBase = this.store.selectSnapshot(AppConfigState.manifest)?.apiBase ?? '/api/v1';\n let path = url.split('?')[0];\n if (/^https?:\\/\\//.test(path)) {\n try {\n const parsed = new URL(path);\n if (typeof window !== 'undefined' && parsed.host !== window.location.host) return false;\n path = parsed.pathname;\n } catch {\n return false;\n }\n }\n return path === apiBase || path.startsWith(`${apiBase}/`);\n }\n\n /** Ask the server. Applies the answer and announces it when it differs. */\n refresh(): Observable<ElevationState> {\n const url = this.url;\n if (!url) return of(this.inert());\n\n return this.http.get<ElevationState>(url, this.options()).pipe(\n tap(s => this.apply(s)),\n );\n }\n\n /**\n * Elevate with the admin password and, when the switch is on, a code from\n * the admin authenticator. Errors are the dialog's to branch on: 403 wrong\n * password or wrong / replayed code, 428 a code is required, 409 the\n * installation cannot elevate yet, 429 throttled. They pass through\n * untouched, and never open the prompt (BYPASS_ELEVATION).\n */\n elevate(password: string, code?: string): Observable<ElevationState> {\n const url = this.url;\n if (!url) return of(this.inert());\n\n const body: { password: string; code?: string } = { password };\n if (code) body.code = code;\n\n return this.http.post<ElevationState>(url, body, this.options()).pipe(\n tap(s => this.apply(s, 'granted')),\n );\n }\n\n /** Drop the elevation now. Idempotent on the server: nothing live is still a 204. */\n drop(reason: ElevationDropReason = 'dropped_by_user'): Observable<ElevationState> {\n const url = this.url;\n if (!url) return of(this.inert());\n\n return this.http.delete<void>(`${url}?reason=${reason}`, this.options()).pipe(\n switchMap(() => this.refresh()),\n );\n }\n\n /**\n * What the interceptor calls on a 403: ask the server whether this session\n * is elevated, and only when it is not, offer the prompt. A 403 while\n * elevated is a real refusal -- elevation would not change it -- and is\n * handed back as such (false, no prompt).\n */\n offerFor(refusal?: string): Observable<boolean> {\n if (!this.available || !this.prompt) return of(false);\n\n return this.refresh().pipe(\n switchMap(state => state.elevated ? of(false) : this.requestElevation({ state, refusal })),\n );\n }\n\n /**\n * Open the prompt, or join the one already open. Resolves to whether the\n * session is elevated once the dialog closes.\n */\n requestElevation(request: ElevationPromptRequest): Observable<boolean> {\n if (!this.prompt) return of(false);\n if (this.pendingPrompt) return this.pendingPrompt;\n\n const prompt = this.prompt;\n this.pendingPrompt = defer(() => prompt.open(request)).pipe(\n finalize(() => { this.pendingPrompt = null; }),\n shareReplay(1),\n );\n\n return this.pendingPrompt;\n }\n\n // -- internals ------------------------------------------------------------\n\n private options(): { context: HttpContext } {\n return { context: new HttpContext().set(BYPASS_ELEVATION, true) };\n }\n\n private inert(): ElevationState {\n return {\n elevated: false,\n ended: { reason: 'never', at: null },\n lifetimeSeconds: 0,\n lifetimeCeilingSeconds: 0,\n mfaRequired: false,\n warnings: [],\n };\n }\n\n /**\n * Store the answer, schedule the expiry read, announce a change.\n *\n * `explicit` names the change when the caller knows it (a grant). For a\n * plain refresh the kind is derived from the two states: elevated -> not\n * is `expired` when the server says so and `dropped` otherwise (the person,\n * another tab, the beacon, the tripwire); not -> elevated is a grant seen\n * from another tab; a live grant whose `expiresAt` moved is `observed`.\n *\n * `prev` is read RAW rather than through `state()`: the demotion is not\n * wanted here (this answer supersedes the stored one either way), and a\n * read that demoted would queue an expiry announcement the server's own\n * answer is about to make.\n */\n private apply(next: ElevationState, explicit?: ElevationChangeKind): void {\n const prev = untracked(() => this.served());\n this.served.set(next);\n this.schedule(next);\n\n const kind = explicit ?? this.kindOf(prev, next);\n if (kind === null) return;\n\n // Something is being announced here, so a queued expiry would be a\n // second telling of the same news -- or, after a grant, stale news.\n // A NULL kind must not cancel it: that is the case where a read\n // already demoted the stored answer, leaving this one nothing to\n // compare against, and the queued announcement is the only one there\n // will be.\n this.expiryAnnouncePending = false;\n\n this.changes$.next({ kind, state: next });\n this.announceToOtherTabs();\n }\n\n private kindOf(prev: ElevationState | null, next: ElevationState): ElevationChangeKind | null {\n // Raw: a grant nothing had read yet is still a grant that ENDED, and\n // this answer is the first notice of it. When a read demoted it first,\n // `prev` already says so and this correctly finds no change.\n const was = prev?.elevated ?? false;\n if (was && !next.elevated) return next.ended.reason === 'expired' ? 'expired' : 'dropped';\n if (!was && next.elevated) return 'granted';\n if (was && next.elevated && (prev?.expiresAt ?? null) !== (next.expiresAt ?? null)) return 'observed';\n return null;\n }\n\n /**\n * Tell the listings that this session's elevation ran out, once, and NOT\n * from inside the read that noticed: a subscriber refetches on this, and a\n * refetch started in the middle of a change detection pass is a write\n * during a read. The microtask runs at the end of the current task, before\n * anything can paint.\n *\n * A server answer arriving first clears the flag, so the announcement is\n * made by whichever of the two got there first, never by both. Other tabs\n * are NOT told: each one reaches the same conclusion from the same\n * `expiresAt` on its own read, so the storage write would only buy a\n * thundering herd of GETs.\n */\n private announceExpiryLater(): void {\n if (this.expiryAnnouncePending) return;\n this.expiryAnnouncePending = true;\n\n queueMicrotask(() => {\n if (!this.expiryAnnouncePending) return;\n this.expiryAnnouncePending = false;\n\n const state = untracked(() => this.served());\n if (state !== null && !state.elevated) this.changes$.next({ kind: 'expired', state });\n });\n }\n\n /**\n * Ask the server again at `expiresAt` -- an OPTIMISATION, not the\n * mechanism. `state()` already reports an elapsed grant as expired without\n * being told, so a timer that a sleeping machine swallows, or that a\n * background tab throttles to once a minute, now costs freshness rather\n * than correctness. What it buys is the listing refetch happening at the\n * moment of expiry instead of at the next read.\n *\n * Do not flip a local flag here: the lifetime counts from the grant on the\n * server's clock, and if the server still says elevated at that moment\n * (skew), the honest thing is to ask again shortly.\n */\n private schedule(state: ElevationState): void {\n this.clearTimer();\n if (!state.elevated || !state.expiresAt) return;\n\n // Floor: never a tight loop on a stale answer. Ceiling: a browser\n // treats a delay past 2^31-1 ms as ZERO and fires at once; the code\n // ceiling is an hour, so only bad data gets there, and it re-schedules.\n const wanted = Date.parse(state.expiresAt) - Date.now() + 250;\n const ms = Math.min(Math.max(1_000, wanted), 2_147_483_647);\n this.expiryTimer = setTimeout(() => {\n this.expiryTimer = null;\n this.refresh().subscribe({\n // apply() has re-scheduled from the fresh answer. Only when the\n // server still says elevated PAST its own expiresAt (clock skew\n // between the two) is a short retry needed instead.\n next: s => {\n if (s.elevated && s.expiresAt && Date.parse(s.expiresAt) <= Date.now()) this.scheduleRetry();\n },\n error: () => this.scheduleRetry(),\n });\n }, ms);\n }\n\n /**\n * One more attempt, then stop. Giving up is affordable now and was not\n * before: the state a failed read leaves behind is still read through the\n * arithmetic above, so an unreachable server cannot leave this tab\n * believing it is elevated. What is lost is only news from elsewhere -- a\n * grant or a drop in another session -- and that is what the tab picks up\n * when it next comes into view.\n */\n private scheduleRetry(): void {\n this.clearTimer();\n this.expiryTimer = setTimeout(() => {\n this.expiryTimer = null;\n this.refresh().subscribe({ error: () => undefined });\n }, 5_000);\n }\n\n private clearTimer(): void {\n if (this.expiryTimer !== null) {\n clearTimeout(this.expiryTimer);\n this.expiryTimer = null;\n }\n }\n\n private announceToOtherTabs(): void {\n try {\n localStorage.setItem(CROSS_TAB_KEY, String(Date.now()));\n } catch {\n // Private mode or storage denied: the other tabs learn at their next 403.\n }\n }\n\n private readonly onStorage = (event: StorageEvent): void => {\n if (event.key !== CROSS_TAB_KEY || !this.available) return;\n this.refresh().subscribe({ error: () => undefined });\n };\n\n private readonly onFocus = (): void => this.reconcile();\n\n private readonly onVisibilityChange = (): void => {\n if (document.visibilityState === 'visible') this.reconcile();\n };\n\n /**\n * The tab is back. Read first -- that alone ends an elevation that ran out\n * while it was away, with no request and no network to depend on -- then\n * ask the server for what the read cannot know: a grant or a drop made\n * elsewhere, or a lifetime the server moved.\n */\n private reconcile(): void {\n this.state();\n if (!this.available) return;\n\n const now = Date.now();\n if (now - this.lastReconcileAt < RECONCILE_MIN_INTERVAL_MS) return;\n this.lastReconcileAt = now;\n\n this.refresh().subscribe({ error: () => undefined });\n }\n}\n\n/** Whether a stored answer has gone past the moment the server named. */\nfunction isPast(state: ElevationState): boolean {\n if (!state.elevated || !state.expiresAt) return false;\n\n const at = Date.parse(state.expiresAt);\n // Unparseable is not expired: a date this client cannot read is a reason to\n // keep asking the server, not to end an elevation it may still be granting.\n return Number.isFinite(at) && at <= Date.now();\n}\n\n/**\n * The same answer, read after its own `expiresAt`: what the server would say\n * if asked now, down to `ended`, so a prompt opened from it explains itself\n * the same way whether the news came from the read or from the endpoint.\n */\nfunction expiredView(state: ElevationState): ElevationState {\n return {\n ...state,\n elevated: false,\n expiresAt: null,\n grantedAt: null,\n factorConfirmed: null,\n ended: { reason: 'expired', at: state.expiresAt ?? null },\n };\n}\n","import { Injectable } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\n\n/**\n * Converts any caught error into a human-readable message.\n *\n * Priority order for backend errors:\n * 1. error.detail (RFC 7807 problem detail)\n * 2. error['hydra:description'] (Hydra validation errors)\n * 3. error.message (generic)\n * 4. error.violations[] (constraint violations)\n * 5. Status-based fallback\n */\n@Injectable({ providedIn: 'root' })\nexport class ErrorHandlerService {\n humanize(err: unknown): string {\n if (!(err instanceof HttpErrorResponse)) {\n return err instanceof Error ? err.message : 'An unexpected error occurred.';\n }\n\n const body = err.error as Record<string, unknown> | null;\n if (body && typeof body === 'object') {\n const detail = body['detail'] ?? body['hydra:description'] ?? body['message'];\n if (typeof detail === 'string' && detail.trim()) {\n return detail.trim();\n }\n\n if (Array.isArray(body['violations'])) {\n const msgs = (body['violations'] as Array<{ message: string }>)\n .map(v => v.message)\n .filter(Boolean);\n if (msgs.length) return msgs.join(' ');\n }\n }\n\n return this.statusMessage(err.status);\n }\n\n private statusMessage(status: number): string {\n switch (status) {\n case 0: return 'Network error. Check your connection.';\n case 400: return 'Invalid request.';\n case 401: return 'Session expired. Please sign in again.';\n case 403: return 'Access denied.';\n case 404: return 'Resource not found.';\n case 409: return 'Conflict — resource already exists.';\n case 422: return 'Validation failed.';\n case 429: return 'Too many requests. Please wait a moment.';\n case 500: return 'Server error. Please try again later.';\n case 503: return 'Service unavailable. Please try again later.';\n default: return `Unexpected error (${status}).`;\n }\n }\n}\n","import { inject } from '@angular/core';\nimport { HttpErrorResponse, type HttpInterceptorFn } from '@angular/common/http';\nimport { catchError, switchMap, throwError } from 'rxjs';\nimport { BYPASS_ELEVATION, ELEVATION_REQUIRED_HEADER, ElevationService } from './elevation.service';\nimport { ErrorHandlerService } from '../errors/error-handler.service';\n\n/**\n * The elevation prompt hangs off the 403, not off the listing's flags\n * (the elevation client requirement, point 1) -- and off the 403 that SAYS it\n * was refused for want of elevation: the server stamps `X-Elevation-Required`\n * with the gate's name where its gate refused a member who has not elevated.\n * Any route of this API can carry it -- the VFS, a media asset's\n * permissions, a document template, an account's deletion -- so nothing here\n * lists URLs; the list that stood here lagged the server by every route whose\n * refusal came from the VFS permission service under another module's path.\n *\n * On the stamp: ask the server whether this session is elevated, and only when\n * it is NOT open the prompt; on a grant send the refused request again, once.\n * A 403 without the stamp is a refusal for another reason and is handed back\n * as it came; so are a 403 while elevated, a declined prompt, and an\n * installation that cannot elevate -- nothing here swallows a refusal. A\n * stamped 403 from anywhere but this API is ignored: the header is the\n * server's word, and only this server's counts.\n *\n * Order in the chain: BEFORE the auth interceptor, so the retry re-enters it\n * and carries the token current at that moment (the prompt may have outlived\n * a refresh). The elevation endpoint's own calls carry BYPASS_ELEVATION: a\n * wrong password is a 403 and is not a refused action.\n */\nexport const elevationInterceptor: HttpInterceptorFn = (req, next) => {\n const elevation = inject(ElevationService);\n const errors = inject(ErrorHandlerService);\n\n if (req.context.get(BYPASS_ELEVATION) || !elevation.isThisApi(req.url)) {\n return next(req);\n }\n\n return next(req).pipe(\n catchError((err: unknown) => {\n if (!(err instanceof HttpErrorResponse) || err.status !== 403 || !err.headers.get(ELEVATION_REQUIRED_HEADER)) {\n return throwError(() => err);\n }\n\n return elevation.offerFor(errors.humanize(err)).pipe(\n switchMap(granted => granted ? next(req) : throwError(() => err)),\n );\n }),\n );\n};\n","import { Component, inject } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NgIf } from '@angular/common';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { Login } from '../auth.actions';\n\n@Component({\n selector: 'coolms-admin-login',\n standalone: true,\n imports: [FormsModule, NgIf],\n templateUrl: './login.component.html',\n})\nexport class LoginComponent {\n identifier = '';\n password = '';\n loading = false;\n error: string | null = null;\n\n private readonly store = inject(Store);\n private readonly router = inject(Router);\n\n submit(): void {\n if (!this.identifier || !this.password) return;\n\n this.loading = true;\n this.error = null;\n\n this.store.dispatch(new Login(this.identifier, this.password)).subscribe({\n next: () => {\n // A rejected navigation used to vanish: nothing resets `loading`\n // on the success path, because the page is expected to go\n // away. If it does not, the form spins for ever with no reason\n // given, so say so instead.\n void this.router.navigate(['/']).catch(() => {\n this.loading = false;\n this.error = 'Signed in, but the admin could not be opened. Please reload.';\n });\n },\n error: (err) => {\n this.loading = false;\n this.error = err?.error?.detail ?? err?.error?.message ?? err?.message ?? 'Invalid credentials. Please try again.';\n },\n });\n }\n}\n","<div class=\"d-flex align-items-center justify-content-center min-vh-100 bg-light\">\n <div class=\"card shadow-sm\" style=\"width: 360px\">\n <div class=\"card-body p-4\">\n <h1 class=\"h5 mb-1\">CoolMS Admin</h1>\n <p class=\"text-muted small mb-4\">Sign in to continue</p>\n\n <div *ngIf=\"error\" class=\"alert alert-danger py-2 small\">{{ error }}</div>\n\n <form (ngSubmit)=\"submit()\" #loginForm=\"ngForm\">\n <div class=\"mb-3\">\n <label class=\"form-label\">Email or username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n [(ngModel)]=\"identifier\"\n name=\"identifier\"\n required\n autocomplete=\"username\"\n />\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n [(ngModel)]=\"password\"\n name=\"password\"\n required\n autocomplete=\"current-password\"\n />\n </div>\n <button\n type=\"submit\"\n class=\"cms-btn cms-btn-primary w-100 justify-content-center\"\n [disabled]=\"loading || loginForm.invalid\"\n >\n {{ loading ? 'Signing in…' : 'Sign in' }}\n </button>\n </form>\n </div>\n </div>\n</div>\n","import { inject, Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Store } from '@ngxs/store';\nimport { map, type Observable, shareReplay } from 'rxjs';\nimport { AppConfigState } from '../state/app-config.state';\n\n/**\n * Layout config DTO -- the shape served from\n * `GET /api/v1/config/layout/{id}`. Mirrors the YAML at\n * `config/modules/{module}/layout/*.yaml`.\n *\n * The optional fields below are the page-level contract\n * every admin SPA page resolves through. The base contract (id /\n * template / title / icon / slots) is the legacy list shape and stays\n * untouched. Existing 17 list YAMLs render identically when the new\n * fields are absent.\n *\n * New optional fields:\n * - `subtitle` one-line page description rendered under the title\n * - `sections` ordered map of body sections, each with its own slot graph\n * (replaces single-slot `slots:` for multi-section pages)\n * - `headerActions` buttons displayed in the cms-page-header action bar\n * - `permissions` hint for clients about who this page targets; real gating\n * lives on the data APIs the slot components consume\n * (the layout endpoint itself is public by design --\n * layouts carry only structure, never data)\n *\n * `[key: string]: unknown` index signature stays so existing per-page\n * keys (e.g. `media:library`'s `naviGraph:` slot shorthand) keep working\n * without further interface changes.\n */\nexport interface LayoutHeaderAction {\n id: string;\n label?: string;\n icon?: string;\n title?: string;\n primary?: boolean;\n danger?: boolean;\n disabled?: boolean;\n /**\n * Optional capability gate evaluated FE-side against runtime state\n * (e.g. `administer` / `delete`) -- the config declares WHICH actions\n * exist + their gate; the FE decides if THIS user/row qualifies. Absent\n * = always shown. Used by detail pages whose actions are config-driven\n * (see `web:section-detail`).\n *\n * For a gate that depends on the RECORD rather than the user, prefer\n * `showWhen` below: `requires` needs a page to interpret the token, so\n * every new value costs a branch in TypeScript.\n */\n requires?: string;\n /**\n * Conditions on the page's published context, same grammar and same\n * evaluator as a NaviGraph toolbar node, applied\n * by {@link LayoutActionsService}:\n *\n * showWhen -- the action does not APPLY, so it is absent.\n * disabledWhen -- temporarily UNAVAILABLE: stays put and greys out.\n * busyWhen -- the action is RUNNING: greys out too, and may relabel.\n * busyLabel -- label shown while `busyWhen` holds (\"Saving...\").\n * activeWhen -- pressed/active styling.\n *\n * Two carriers (a layout config and a NaviGraph tree) but ONE vocabulary:\n * a page that moves between them should not have to relearn how to say\n * \"not now\".\n */\n showWhen?: Record<string, unknown>;\n disabledWhen?: Record<string, unknown>;\n busyWhen?: Record<string, unknown>;\n busyLabel?: string;\n activeWhen?: Record<string, unknown>;\n}\n\nexport interface LayoutSection {\n title?: string;\n /**\n * When true, the layout shell pins the section to the top of the\n * scroll container (CSS `position: sticky`). Used by inspector /\n * filter pages where an input form should stay visible while the\n * operator scrolls through results below. Currently honoured by\n * cms-inspector-layout; cms-detail-layout will follow when it\n * lands. Default false.\n */\n sticky?: boolean;\n slots?: Record<string, { component?: string; [key: string]: unknown }>;\n [key: string]: unknown;\n}\n\nexport interface LayoutConfig {\n id?: string;\n template?: string;\n title?: string;\n subtitle?: string;\n icon?: string;\n slots?: Record<string, unknown>;\n sections?: Record<string, LayoutSection>;\n headerActions?: LayoutHeaderAction[];\n /**\n * Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)\n * -- primary / destructive actions kept out of the cramped header.\n * Same shape as `headerActions`; gated per-item via `requires`.\n */\n footerActions?: LayoutHeaderAction[];\n permissions?: string[];\n [key: string]: unknown;\n}\nexport interface DialogConfig { [key: string]: unknown; }\nexport interface NavigraphConfig { [key: string]: unknown; }\nexport interface FormConfig { [key: string]: unknown; }\nexport interface DatagridConfig { [key: string]: unknown; }\n\n/**\n * Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.\n *\n * Responses are cached for the lifetime of the service (Map + shareReplay(1))\n * so repeated calls never re-fetch the same resource.\n *\n * configBase is read from the API manifest (manifest.configBase) and falls\n * back to '/api/v1/config' when the manifest is not yet loaded.\n */\n@Injectable({ providedIn: 'root' })\nexport class ConfigService {\n private readonly http = inject(HttpClient);\n private readonly store = inject(Store);\n private readonly cache = new Map<string, Observable<unknown>>();\n\n layout(id: string): Observable<LayoutConfig> { return this.fetch<LayoutConfig>('layout', id); }\n dialog(id: string): Observable<DialogConfig> { return this.fetch<DialogConfig>('dialog', id); }\n navigraph(slug: string): Observable<NavigraphConfig> { return this.fetch<NavigraphConfig>('navigraph', slug); }\n form(id: string): Observable<FormConfig> { return this.fetch<FormConfig>('form', id); }\n datagrid(id: string): Observable<DatagridConfig> { return this.fetch<DatagridConfig>('datagrid', id); }\n\n private fetch<T>(type: string, id: string): Observable<T> {\n const key = `${type}/${id}`;\n\n if (!this.cache.has(key)) {\n const base = this.store.selectSnapshot(AppConfigState.manifest)?.configBase\n ?? '/api/v1/config';\n // The config endpoint wraps the YAML payload in a `data` field:\n // { type, id, data: { ...yaml content... } }\n // Unwrap so callers always receive the config payload directly.\n const obs = this.http.get<{ data?: T } & T>(`${base}/${type}/${id}`).pipe(\n map(r => (r.data ?? r) as T),\n shareReplay(1),\n );\n this.cache.set(key, obs);\n }\n\n return this.cache.get(key) as Observable<T>;\n }\n}\n","import { computed, effect, inject, Injectable, signal } from '@angular/core';\nimport { type Observable, of } from 'rxjs';\nimport { catchError, map, shareReplay, tap } from 'rxjs/operators';\nimport { Store } from '@ngxs/store';\n\nimport { IdentityApiClient } from '../api/identity-api.client';\nimport { AppConfigState } from '../state/app-config.state';\n\n/** What the user PICKED. `system` defers to the OS. */\nexport type ThemeChoice = 'light' | 'dark' | 'system';\n\n/** What that resolves to right now -- the only two the stylesheet knows. */\nexport type ResolvedTheme = 'light' | 'dark';\n\nconst CHOICES: readonly ThemeChoice[] = ['light', 'dark', 'system'];\n\n/**\n * A CACHE, never a source of truth.\n *\n * The authoritative value is `preferences.theme` in `/auth/me/settings`, which\n * the Profile -> Preferences tab already writes. This copy exists only so the\n * first paint of a reload is the right colour: the settings request is async,\n * so without it every load would start light and flip. It is deliberately NOT\n * stored via UserPreferencesService, which syncs its own bag to the server --\n * that would give one setting two server-side homes.\n */\nconst CACHE_KEY = 'coolms_theme';\nconst ACCENT_CACHE_KEY = 'coolms_accent';\n\n/**\n * Six-digit hex only, checked again HERE even though the server already vets it\n * on write. This value is substituted into a `--cms-*` custom property,\n * and a stored setting is data from the network like any other -- the write-side\n * guard protects what the admin stores, this one protects what it renders.\n */\nconst HEX = /^#[0-9a-fA-F]{6}$/;\n\nfunction isChoice(v: unknown): v is ThemeChoice {\n return typeof v === 'string' && (CHOICES as readonly string[]).includes(v);\n}\n\nfunction isAccent(v: unknown): v is string {\n return typeof v === 'string' && HEX.test(v);\n}\n\n/**\n * Applies the user's theme by setting `data-theme` on the document element.\n *\n * The stylesheet does the rest: `:root[data-theme='dark']` re-points the\n * `--cms-*` palette and every colour in the admin arrives through those names.\n * Nothing here knows a single colour, which is the point -- a second\n * theme is a block of tokens, not a code change.\n *\n * Follows the shape of UserCalendarPreferencesService and\n * CallOverlayPreferencesService: lazy one-shot load of `/auth/me/settings`,\n * cached for the SPA lifetime, with `update()` called by the Profile page after\n * a save so the change lands without a refetch.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeService {\n private readonly api = inject(IdentityApiClient);\n private readonly store = inject(Store);\n\n private readonly _choice = signal<ThemeChoice>(ThemeService.readCache());\n private readonly _systemDark = signal(false);\n private readonly _userAccent = signal<string | null>(ThemeService.readAccentCache());\n private readonly _platformAccent = signal<string | null>(null);\n\n private loadOnce$?: Observable<ThemeChoice>;\n\n /** The user's stored choice, including `system`. */\n readonly choice = this._choice.asReadonly();\n\n /** This user's personal override, independent of the deployment's. */\n readonly userAccent = this._userAccent.asReadonly();\n\n /** The deployment's brand accent from the API manifest, if it set one. */\n readonly platformAccent = this._platformAccent.asReadonly();\n\n /**\n * The colour actually painted: personal override, else the deployment's,\n * else null -- which leaves the stylesheet's own accent in place.\n *\n * Three rungs and each is a real state. A user who has chosen nothing is\n * NOT the same as one who chose the deployment's colour: clear the\n * deployment's brand and the first user follows it, the second does not.\n */\n readonly accent = computed<string | null>(() => this._userAccent() ?? this._platformAccent());\n\n /** The choice with `system` resolved against the OS setting. */\n readonly resolved = computed<ResolvedTheme>(() => {\n const c = this._choice();\n\n return c === 'system' ? (this._systemDark() ? 'dark' : 'light') : c;\n });\n\n constructor() {\n // `matchMedia` is guarded because the unit suite and any SSR-ish\n // rendering path may not provide it; absent it, `system` means light.\n const mq = typeof window !== 'undefined' && window.matchMedia\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null;\n\n if (mq) {\n this._systemDark.set(mq.matches);\n // The OS can flip while the SPA is open, and `system` must follow it\n // live rather than only at load.\n mq.addEventListener('change', e => this._systemDark.set(e.matches));\n }\n\n effect(() => this.applyToDocument(this.resolved()));\n effect(() => this.applyAccent(this.accent()));\n }\n\n /**\n * One-shot load of the stored preference. Safe to call more than once and\n * safe for anonymous users -- a failure leaves the cached/system value in\n * place rather than forcing light, so the login screen still matches the OS.\n */\n ensureLoaded(): Observable<ThemeChoice> {\n if (this.loadOnce$) return this.loadOnce$;\n\n // Read the deployment's colour FIRST and synchronously, so it is in\n // place whether or not the settings request succeeds -- an anonymous\n // visitor on the login screen still sees the deployment's brand.\n this.readPlatformAccent();\n\n this.loadOnce$ = this.api.getSettings().pipe(\n map(all => {\n const prefs = all['preferences'] as Record<string, unknown> | undefined;\n // The accent rides along on the same response -- it is a field of\n // the same section, so it costs no extra request.\n this.setAccent(isAccent(prefs?.['accentColor']) ? prefs['accentColor'] : null);\n\n return isChoice(prefs?.['theme']) ? prefs['theme'] : this._choice();\n }),\n tap(choice => this.set(choice)),\n catchError(() => of(this._choice())),\n shareReplay({ bufferSize: 1, refCount: false }),\n );\n\n return this.loadOnce$;\n }\n\n /**\n * Called by the Profile page after the Preferences tab saves, so the admin\n * re-themes on the spot. Ignores anything that is not a known choice --\n * the section PATCH returns the whole merged bag, not just this field.\n */\n update(value: unknown): void {\n if (isChoice(value)) this.set(value);\n }\n\n /**\n * Companion to {@link update} for the accent field of the same save.\n * An explicit null clears the override; anything malformed is ignored\n * rather than applied, so a bad value can never blank the admin's accent.\n */\n updateAccent(value: unknown): void {\n if (null === value || '' === value) this.setAccent(null);\n else if (isAccent(value)) this.setAccent(value);\n }\n\n private set(choice: ThemeChoice): void {\n this._choice.set(choice);\n try {\n localStorage.setItem(CACHE_KEY, choice);\n } catch {\n // A blocked or full localStorage costs the pre-paint hint, nothing\n // more -- the server value still arrives and applies.\n }\n }\n\n /**\n * The manifest is loaded at bootstrap, well before the authenticated shell\n * calls this, so a snapshot read is enough and no subscription is needed.\n * A malformed value is ignored rather than applied -- the container refuses\n * to build with one, but the manifest crosses the network all the same.\n */\n private readPlatformAccent(): void {\n const value = this.store.selectSnapshot(AppConfigState.manifest)?.platformDefaults?.accentColor;\n this._platformAccent.set(isAccent(value) ? value : null);\n }\n\n private setAccent(accent: string | null): void {\n this._userAccent.set(accent);\n try {\n if (accent) localStorage.setItem(ACCENT_CACHE_KEY, accent);\n else localStorage.removeItem(ACCENT_CACHE_KEY);\n } catch {\n // Same as the theme cache: losing it costs the pre-paint hint only.\n }\n }\n\n /**\n * Writes the override onto the document element, where an inline style\n * outranks every stylesheet rule -- including the dark block -- so one\n * declaration re-points the accent in both themes.\n *\n * Only --cms-accent is set, not the -hover/-light/-text members of the\n * family. Deriving those from an arbitrary user colour needs a colour model\n * the palette does not have yet, and guessing them would produce hover\n * states that clash with the very colour the user chose. Until then the\n * override moves the brand fill and leaves its supporting tones alone.\n */\n private applyAccent(accent: string | null): void {\n const root = document.documentElement;\n if (accent && HEX.test(accent)) root.style.setProperty('--cms-accent', accent);\n else root.style.removeProperty('--cms-accent');\n }\n\n private applyToDocument(theme: ResolvedTheme): void {\n // Written for BOTH values rather than only removing the attribute for\n // light, so the attribute is a reliable signal for anything that wants\n // to read the active theme, and so a future `[data-theme='light']`\n // block has something to hang on.\n document.documentElement.setAttribute('data-theme', theme);\n }\n\n private static readCache(): ThemeChoice {\n try {\n const raw = localStorage.getItem(CACHE_KEY);\n\n return isChoice(raw) ? raw : 'system';\n } catch {\n return 'system';\n }\n }\n\n private static readAccentCache(): string | null {\n try {\n const raw = localStorage.getItem(ACCENT_CACHE_KEY);\n\n return isAccent(raw) ? raw : null;\n } catch {\n return null;\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n/**\n * The platform's loading indicator: the exclamation mark from Cool!MS,\n * drawing itself.\n *\n * ## Why a mark and not a spinner\n *\n * A spinner says \"something is happening\" and nothing else; every product has\n * one and none of them are yours. The bang is already the one glyph in the\n * wordmark that carries the brand, so a loader built from it says whose\n * software is thinking -- for free, in the place a user is already looking.\n *\n * ## Why one component rather than a snippet per surface\n *\n * It is meant for every wait long enough to notice: a dashboard assembling its\n * cards, an editor paginating a document, an explorer listing a folder. Those\n * are different teams' files, and a copied SVG in each is how three loaders\n * that no longer match each other happen.\n *\n * ## The animation\n *\n * The stem wipes upward and the dot lands after it, on a loop -- the shape being\n * WRITTEN rather than spun. It is deliberately calm: a loader is background\n * furniture, and anything with a hard beat gets irritating on the third viewing.\n * Under `prefers-reduced-motion` it holds still and only breathes, because\n * motion sickness is not a stylistic preference.\n */\n@Component({\n selector: 'cms-loader',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"cms-loader\"\n [class.cms-loader--overlay]=\"overlay()\"\n [class.cms-loader--inline]=\"inline()\"\n role=\"status\"\n [attr.aria-label]=\"label() || 'Loading'\">\n <svg class=\"cms-loader__mark\" viewBox=\"0 0 24 48\" aria-hidden=\"true\">\n <defs>\n <clipPath [attr.id]=\"clipId\">\n <rect class=\"cms-loader__wipe\" x=\"0\" y=\"0\" width=\"24\" height=\"34\" />\n </clipPath>\n </defs>\n <!-- Ghost beneath: the mark stays legible as a SHAPE while the\n wipe crosses it, so the loader never looks broken mid-cycle. -->\n <rect class=\"cms-loader__ghost\" x=\"8\" y=\"2\" width=\"8\" height=\"30\" rx=\"4\" />\n <circle class=\"cms-loader__ghost\" cx=\"12\" cy=\"42\" r=\"4\" />\n\n <rect class=\"cms-loader__stem\" x=\"8\" y=\"2\" width=\"8\" height=\"30\" rx=\"4\"\n [attr.clip-path]=\"'url(#' + clipId + ')'\" />\n <circle class=\"cms-loader__dot\" cx=\"12\" cy=\"42\" r=\"4\" />\n </svg>\n @if (label()) {\n <span class=\"cms-loader__label\">{{ label() }}</span>\n }\n </div>\n `,\n styles: [`\n .cms-loader {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 24px;\n color: var(--cms-text-muted);\n }\n /* Fills whatever it is dropped into and paints the surface behind it,\n which is what makes it usable as a COVER for content that is not\n ready to be seen rather than only as a placeholder in empty space. */\n .cms-loader--overlay {\n position: absolute;\n inset: 0;\n z-index: 5;\n background: var(--cms-surface);\n }\n /* Inline: the mark sits BESIDE its label at text scale, for the waits\n that happen inside a row, a status bar or a button rather than in an\n empty panel. Same mark, so a page does not switch identities between\n its big wait and its small one. */\n .cms-loader--inline {\n flex-direction: row;\n gap: 8px;\n padding: 0;\n }\n .cms-loader--inline .cms-loader__mark {\n width: var(--cms-loader-size, 14px);\n }\n .cms-loader__mark {\n width: var(--cms-loader-size, 40px);\n height: auto;\n overflow: visible;\n }\n .cms-loader__ghost {\n fill: var(--cms-border);\n }\n .cms-loader__stem, .cms-loader__dot {\n fill: var(--cms-accent);\n }\n .cms-loader__wipe {\n animation: cms-loader-wipe 1.6s cubic-bezier(.65, 0, .35, 1) infinite;\n transform-origin: 12px 32px;\n }\n .cms-loader__dot {\n animation: cms-loader-dot 1.6s cubic-bezier(.65, 0, .35, 1) infinite;\n transform-origin: 12px 42px;\n }\n /* The stem is written from the base up: scaled about its own foot, so\n it grows out of the baseline instead of sliding into place. */\n @keyframes cms-loader-wipe {\n 0% { transform: scaleY(0); }\n 45%, 70% { transform: scaleY(1); }\n 100% { transform: scaleY(0); }\n }\n /* The dot lands after the stem has finished, then leaves with it. */\n @keyframes cms-loader-dot {\n 0%, 40% { opacity: 0; transform: scale(.4); }\n 55%, 70% { opacity: 1; transform: scale(1); }\n 100% { opacity: 0; transform: scale(.4); }\n }\n .cms-loader__label {\n font-size: .8125rem;\n }\n /* Motion sickness is not a stylistic preference: the mark holds its\n shape and only breathes. */\n @media (prefers-reduced-motion: reduce) {\n .cms-loader__wipe { animation: none; transform: scaleY(1); }\n .cms-loader__dot { animation: cms-loader-breathe 2s ease-in-out infinite; opacity: 1; }\n }\n @keyframes cms-loader-breathe {\n 0%, 100% { opacity: .35; }\n 50% { opacity: 1; }\n }\n `],\n})\nexport class CmsLoaderComponent {\n /** Optional caption. Absent renders the mark alone, which suits a small inline wait. */\n readonly label = input<string>('');\n\n /** Cover the positioned ancestor rather than sitting in the flow. */\n readonly overlay = input<boolean>(false);\n\n /**\n * Row layout at text scale, for a wait inside a row, a status bar or a\n * button. Override `--cms-loader-size` to tune the mark.\n */\n readonly inline = input<boolean>(false);\n\n /**\n * A clip-path needs a document-unique id, and this component is used more\n * than once per page -- two loaders sharing an id makes the second one clip\n * against the first one's rectangle and stop animating.\n */\n protected readonly clipId = 'cms-loader-' + Math.random().toString(36).slice(2, 9);\n}\n","import { Injectable, signal } from '@angular/core';\n\n/**\n * Sidebar collapse state -- two independent dimensions:\n *\n * - `collapsed`: whole-sidebar icon-only rail toggle (the chevron at\n * the bottom of the sidebar). Per-device localStorage.\n * - `collapsedSections`: per-section collapse, keyed by the section\n * separator's node id. Used to fold a section's items under their\n * header so the nav doesn't endless-scroll once a tenant has lots\n * of modules. Per-device localStorage.\n * - `collapsedItems`: per-item inline-expand collapse, keyed by the\n * nav node's id. Mirrors `collapsedSections` but for sub-tree items\n * (a top-level item with children, e.g. Reports -> Process Report).\n * Absent id = expanded (children visible); present = collapsed.\n * Per-device localStorage.\n *\n * Storage failures (private mode, quota, disabled cookies) fall back\n * to defaulting un-collapsed -- the toggles still work for the session,\n * just don't persist.\n */\n@Injectable({ providedIn: 'root' })\nexport class SidebarStateService {\n private static readonly STORAGE_KEY = 'coolms.admin.sidebar.collapsed';\n private static readonly STORAGE_KEY_SECTIONS = 'coolms.admin.sidebar.collapsedSections';\n private static readonly STORAGE_KEY_ITEMS = 'coolms.admin.sidebar.collapsedItems';\n\n readonly collapsed = signal<boolean>(this.readPersisted());\n readonly collapsedSections = signal<Set<string>>(this.readSectionsPersisted());\n readonly collapsedItems = signal<Set<string>>(this.readItemsPersisted());\n\n toggle(): void {\n const next = !this.collapsed();\n this.collapsed.set(next);\n this.persist(next);\n }\n\n isSectionCollapsed(id: string): boolean {\n return this.collapsedSections().has(id);\n }\n\n toggleSection(id: string): void {\n const next = new Set(this.collapsedSections());\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n this.collapsedSections.set(next);\n this.persistSections(next);\n }\n\n /**\n * Force a section to be expanded. Used when navigation lands on\n * a route inside a manually-collapsed section -- it would be\n * confusing to leave the active item hidden. The user can\n * collapse the section again afterwards.\n */\n ensureExpanded(id: string): void {\n if (!this.collapsedSections().has(id)) return;\n const next = new Set(this.collapsedSections());\n next.delete(id);\n this.collapsedSections.set(next);\n this.persistSections(next);\n }\n\n // --- Per-item inline-expand (sub-tree items, e.g. a section's children) --\n\n isItemCollapsed(id: string): boolean {\n return this.collapsedItems().has(id);\n }\n\n toggleItem(id: string): void {\n const next = new Set(this.collapsedItems());\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n this.collapsedItems.set(next);\n this.persistItems(next);\n }\n\n private readPersisted(): boolean {\n try {\n return localStorage.getItem(SidebarStateService.STORAGE_KEY) === 'true';\n } catch {\n return false;\n }\n }\n\n private persist(value: boolean): void {\n try {\n localStorage.setItem(SidebarStateService.STORAGE_KEY, String(value));\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n\n private readSectionsPersisted(): Set<string> {\n try {\n const raw = localStorage.getItem(SidebarStateService.STORAGE_KEY_SECTIONS);\n if (raw === null) return new Set();\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return new Set();\n return new Set(parsed.filter((v): v is string => typeof v === 'string'));\n } catch {\n return new Set();\n }\n }\n\n private persistSections(value: Set<string>): void {\n try {\n localStorage.setItem(\n SidebarStateService.STORAGE_KEY_SECTIONS,\n JSON.stringify(Array.from(value)),\n );\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n\n private readItemsPersisted(): Set<string> {\n try {\n const raw = localStorage.getItem(SidebarStateService.STORAGE_KEY_ITEMS);\n if (raw === null) return new Set();\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return new Set();\n return new Set(parsed.filter((v): v is string => typeof v === 'string'));\n } catch {\n return new Set();\n }\n }\n\n private persistItems(value: Set<string>): void {\n try {\n localStorage.setItem(\n SidebarStateService.STORAGE_KEY_ITEMS,\n JSON.stringify(Array.from(value)),\n );\n } catch {\n // Non-persistence acceptable; toggle still works in-session.\n }\n }\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { type Observable } from 'rxjs';\n\n/**\n * One row returned by the `/api/entity-search` endpoint. `id` is\n * what the picker emits via its `valueChange` output (and what the\n * template's persisted context carries). `label` is the primary\n * display line. `secondary` is an optional second line for\n * disambiguation (email, slug, etc.) -- the backend omits the key\n * when there is nothing to show; receiving `null` from a custom\n * resolver is normalized to `undefined` here.\n */\nexport interface EntitySearchResult {\n readonly id: string | number;\n readonly label: string;\n readonly secondary?: string;\n}\n\n/**\n * HTTP client for the Phase 2a `/api/entity-search` endpoint.\n * Returns a stream of search results for the given entity FQCN.\n * Permission filtering happens at the backend resolver layer --\n * this service is a thin RPC.\n */\n@Injectable({ providedIn: 'root' })\nexport class EntitySearchService {\n private readonly http = inject(HttpClient);\n\n /**\n * @param entityType Fully-qualified PHP class name of the entity\n * being searched (e.g. the backend's User entity).\n * @param query Free-text search input. Empty string is\n * valid -- the backend returns the default\n * listing for that entity type.\n * @param limit Max rows to return; clamped server-side to\n * [1, 100].\n */\n search(entityType: string, query: string, limit = 20): Observable<EntitySearchResult[]> {\n const params = new HttpParams()\n .set('type', entityType)\n .set('q', query)\n .set('limit', String(limit));\n\n return this.http.get<EntitySearchResult[]>('/api/entity-search', { params });\n }\n}\n","import { inject, Injectable, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { Store } from '@ngxs/store';\nimport { catchError, map, type Observable, of } from 'rxjs';\nimport { Logout } from '../auth/auth.actions';\nimport { type NaviGraphNode, type NaviGraphNodeFlat } from './navi-graph.types';\nimport { ErrorHandlerService } from '../errors/error-handler.service';\n\n/**\n * Loads, caches, and drives admin NaviGraph trees.\n *\n * Usage:\n * naviGraphService.loadAdminNav(url).subscribe();\n * const items = naviGraphService.adminNav(); // signal<NaviGraphNode[]>\n * naviGraphService.handleClick(node);\n */\n@Injectable({ providedIn: 'root' })\nexport class NaviGraphService {\n private readonly http = inject(HttpClient);\n private readonly router = inject(Router);\n private readonly store = inject(Store);\n private readonly errors = inject(ErrorHandlerService);\n\n /** Resolved root nodes for navi.admin (sidebar). */\n readonly adminNav = signal<NaviGraphNode[]>([]);\n\n /** Resolved root nodes for navi.admin.topbar. */\n readonly topbarNav = signal<NaviGraphNode[]>([]);\n\n private readonly cache = new Map<string, NaviGraphNode[]>();\n\n // -------------------------------------------------------------------------\n\n loadAdminNav(url: string): Observable<NaviGraphNode[]> {\n return this.loadTree(url).pipe(\n map(tree => { this.adminNav.set(tree); return tree; }),\n );\n }\n\n loadTopbarNav(url: string): Observable<NaviGraphNode[]> {\n return this.loadTree(url).pipe(\n map(tree => { this.topbarNav.set(tree); return tree; }),\n );\n }\n\n // -------------------------------------------------------------------------\n\n /**\n * Handle a nav-node click.\n *\n * Routing logic based on meta.target:\n * 'action.logout' -- dispatch Logout\n * '_blank' -- window.open (uses node.path as href)\n * default -- router.navigate with meta.routerLink or node.path\n */\n handleClick(node: NaviGraphNode): void {\n const target = node.meta.target;\n\n if (target === 'action.logout') {\n this.store.dispatch(new Logout()).subscribe(() => {\n void this.router.navigate(['/login']);\n });\n return;\n }\n\n if (target === '_blank') {\n window.open(node.meta.routerLink ?? node.path, '_blank', 'noopener');\n return;\n }\n\n const link = node.meta.routerLink ?? node.path;\n void this.router.navigate([link]);\n }\n\n // -------------------------------------------------------------------------\n\n /**\n * Evaluate `showWhen` meta condition against a record.\n * Nodes with no `showWhen` are always visible.\n * Supports nested AND/OR logic: { and: [...] } / { or: [...] }\n */\n isVisible(node: NaviGraphNode, record: Record<string, unknown>): boolean {\n const showWhen = node.meta['showWhen'] as Record<string, unknown> | undefined;\n if (!showWhen) return true;\n return this.evalCondition(showWhen, record);\n }\n\n /**\n * Standalone `showWhen` evaluator for callers that don't have a\n * full `NaviGraphNode` (e.g. a grid's row context menu -- a\n * `rowAction.showWhen` predicate is evaluated against the row's\n * own projection). `null`/`undefined` predicates default to\n * visible, mirroring `isVisible`'s \"no rule = always shown\"\n * semantics.\n */\n matchesShowWhen(showWhen: Record<string, unknown> | undefined | null, record: Record<string, unknown>): boolean {\n if (!showWhen) return true;\n return this.evalCondition(showWhen, record);\n }\n\n private evalCondition(cond: Record<string, unknown>, record: Record<string, unknown>): boolean {\n if ('and' in cond) {\n return (cond['and'] as Record<string, unknown>[])\n .every(c => this.evalCondition(c, record));\n }\n if ('or' in cond) {\n return (cond['or'] as Record<string, unknown>[])\n .some(c => this.evalCondition(c, record));\n }\n // Strict missing-field semantics. A leaf predicate\n // referencing a field absent from the record evaluates to\n // false (fail-loud) instead of the prior op-dependent lax\n // behavior that returned true for ne/nin/unknown ops. Aligns\n // with CSS attribute selectors / JSONPath / React conditional\n // rendering conventions.\n const field = cond['field'] as string;\n if (!(field in record)) return false;\n const val = record[field];\n switch (cond['op']) {\n case 'eq': return val === cond['value'];\n case 'ne': return val !== cond['value'];\n case 'in': return Array.isArray(cond['value']) && (cond['value'] as unknown[]).includes(val);\n case 'nin': return Array.isArray(cond['value']) && !(cond['value'] as unknown[]).includes(val);\n // Prefix match for strings -- primary use case is filtering\n // actions by MIME-type major (e.g., \"image/\" matches all\n // image MIMEs without enumerating each subtype).\n case 'startsWith':\n return typeof val === 'string'\n && typeof cond['value'] === 'string'\n && val.startsWith(cond['value']);\n // Strict: unknown / typo op (e.g. 'equals' instead of 'eq')\n // never matches.\n default: return false;\n }\n }\n\n /**\n * Fetch a graph endpoint and return a nested tree.\n * Handles both compact JSON-LD IRIs (`member`) and\n * the legacy Hydra key (`hydra:member`).\n * Results are cached by URL for the lifetime of the service instance.\n */\n loadTree(url: string): Observable<NaviGraphNode[]> {\n if (this.cache.has(url)) {\n return of(this.cache.get(url)!);\n }\n\n return this.http.get<Record<string, unknown>>(url, {\n headers: { Accept: 'application/ld+json' },\n }).pipe(\n map(r => {\n const raw = r['member'] ?? r['hydra:member'] ?? [];\n const flat: NaviGraphNodeFlat[] = Array.isArray(raw) ? raw as NaviGraphNodeFlat[] : [];\n const tree = this.buildTree(flat);\n this.cache.set(url, tree);\n return tree;\n }),\n catchError(err => {\n console.error('[NaviGraph] loadTree failed for', url, this.errors.humanize(err));\n return of([]);\n }),\n );\n }\n\n /**\n * Build a nested tree from a flat array of nodes.\n * Nodes are sorted by sortOrder at each level.\n */\n private buildTree(flat: NaviGraphNodeFlat[]): NaviGraphNode[] {\n if (!flat.length) {\n return [];\n }\n\n const map = new Map<string, NaviGraphNode>(\n flat.map(n => [n.id, { ...n, children: [] }]),\n );\n const roots: NaviGraphNode[] = [];\n\n for (const node of map.values()) {\n if (node.parentId && map.has(node.parentId)) {\n map.get(node.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n const sort = (nodes: NaviGraphNode[]): void => {\n nodes.sort((a, b) => a.sortOrder - b.sortOrder);\n nodes.forEach(n => n.children?.length && sort(n.children));\n };\n sort(roots);\n\n return roots;\n }\n}\n","import { Injectable, type Type } from '@angular/core';\n\n/**\n * Registry mapping string keys to Angular component classes.\n *\n * Backed by a static Map so components registered at module load time\n * (e.g. in app.config.ts) are visible to all injected instances.\n *\n * Usage -- static (app.config.ts / bootstrap time):\n * ComponentRegistry.register('MediaLibraryPage', MediaLibraryPage);\n *\n * Usage -- injected (SlotComponent / runtime):\n * private readonly registry = inject(ComponentRegistry);\n * const cls = this.registry.get('MediaLibraryPage');\n */\n@Injectable({ providedIn: 'root' })\nexport class ComponentRegistry {\n private static readonly map = new Map<string, Type<unknown>>();\n\n // -- Static API (bootstrap / app.config.ts) -------------------------------\n\n static register(key: string, component: Type<unknown>): void {\n ComponentRegistry.map.set(key, component);\n }\n\n static get(key: string): Type<unknown> | undefined {\n return ComponentRegistry.map.get(key);\n }\n\n static has(key: string): boolean {\n return ComponentRegistry.map.has(key);\n }\n\n static keys(): string[] {\n return Array.from(ComponentRegistry.map.keys());\n }\n\n // -- Instance API (injectable) ---------------------------------------------\n\n register(key: string, component: Type<unknown>): void {\n ComponentRegistry.register(key, component);\n }\n\n get(key: string): Type<unknown> | null {\n return ComponentRegistry.get(key) ?? null;\n }\n\n has(key: string): boolean {\n return ComponentRegistry.has(key);\n }\n\n keys(): string[] {\n return ComponentRegistry.keys();\n }\n}\n","import type { EnvironmentProviders, InjectionToken, Provider, Type } from '@angular/core';\nimport type { Routes } from '@angular/router';\n\n/**\n * `console` -- the administration host contract (the platform rule: hosts implement contracts, modules offer entries).\n *\n * A module contributes to the console with ONE object, its entry: data the\n * host reads, never a call the module makes into the host. Six lists, each\n * optional; an absent list means \"nothing here\", never an error. The host\n * assembles the entries (build time today, install time under federation),\n * refuses collisions by name, and activates only the modules the backend's\n * manifest says are installed.\n *\n * The version is MAJOR.MINOR: a minor adds an optional list or field, a major\n * changes or removes one. An entry declares the range it was written for\n * (`^1.2` -- major 1, minor at least 2), checked at build against this\n * constant and at install against the theme's declaration.\n */\nexport const CONSOLE_CONTRACT = { name: 'console', version: '1.0' } as const;\n\nexport interface ConsoleEntry {\n /** The backend module id, as `config/modules/<id>`. Activation is by this id. */\n readonly module: string;\n readonly contract: 'console';\n /** The console versions this entry was written for: a caret range over MAJOR.MINOR, e.g. '^1.0'. */\n readonly range: string;\n\n readonly routes?: readonly ConsoleRouteMount[];\n readonly bindings?: readonly ConsoleBinding[];\n readonly topbar?: readonly ConsoleTopbarItem[];\n readonly overlays?: readonly ConsoleOverlay[];\n readonly panels?: readonly ConsolePanel[];\n readonly states?: readonly Type<unknown>[];\n readonly providers?: readonly ConsoleProvision[];\n}\n\n/** What a route's `data` carries for the shell: the sidebar item to light, and whether the page owns its height. */\nexport interface ConsoleNavHint {\n readonly activeNav?: string;\n readonly fullHeight?: boolean;\n /** An intermediate crumb the top bar renders between Home and the page. */\n readonly breadcrumb?: { readonly label: string; readonly routerLink: string };\n /** Which server layout the page renders, for the generic layout pages. */\n readonly layoutId?: string;\n}\n\n/**\n * A route mounted under the console's authenticated layout. The host adds\n * what a module may not decide: the layout component, `canActivate: [authGuard]`,\n * and a `canMatch` that answers whether the module is installed here. Routes\n * are lazy: a static component here would put every module's pages in `main`.\n */\nexport type ConsoleRouteMount =\n | {\n readonly path: string;\n readonly children: () => Promise<Routes>;\n readonly nav?: ConsoleNavHint;\n readonly providers?: readonly Provider[];\n }\n | {\n readonly path: string;\n readonly component: () => Promise<Type<unknown>>;\n readonly nav?: ConsoleNavHint;\n readonly providers?: readonly Provider[];\n }\n | {\n readonly path: string;\n readonly redirectTo: string;\n };\n\n/** A `ComponentRegistry` name -> class: names a module OWNS (its server layouts name them) and names it fills in another module's slot (`profile.tab:call`). */\nexport interface ConsoleBinding {\n readonly name: string;\n readonly component: Type<unknown>;\n}\n\n/** A tile in the top bar's quick-access strip, rendered in ascending `order`. */\nexport interface ConsoleTopbarItem {\n readonly id: string;\n readonly order: number;\n readonly component: Type<unknown>;\n}\n\n/** Mounted once, inside the authenticated layout, outside the router outlet. */\nexport interface ConsoleOverlay {\n readonly id: string;\n readonly component: Type<unknown>;\n}\n\n/**\n * A dock panel the layout opens and closes: the host renders its toggle in\n * the top bar and the panel in the dock, and the panel talks back through\n * {@link ConsolePanelHost} -- not through outputs, which a slot cannot carry.\n */\nexport interface ConsolePanel {\n readonly id: string;\n readonly dock: 'bottom';\n /** The top-bar toggle: a label (the title), an icon name or a short text, and a key the host binds with Ctrl. */\n readonly toggle: { readonly label: string; readonly icon?: string; readonly text?: string; readonly key?: string };\n readonly component: Type<unknown>;\n}\n\n/**\n * What a `provide*` line is: environment providers from a library's registry\n * (editor extensions, viewers, field widgets, file editors), or the\n * implementation of a single-implementation port, listed as such so the\n * assembly can refuse two modules implementing one port by name.\n */\nexport type ConsoleProvision =\n | EnvironmentProviders\n | Provider\n | ConsolePortProvision;\n\nexport interface ConsolePortProvision {\n readonly port: InjectionToken<unknown>;\n readonly useExisting: Type<unknown>;\n}\n\nexport function isPortProvision(p: ConsoleProvision): p is ConsolePortProvision {\n return typeof p === 'object' && 'port' in p && 'useExisting' in p;\n}\n\n/** The identity function with the type: an entry file exports data, nothing runs. */\nexport function consoleEntry(entry: ConsoleEntry): ConsoleEntry {\n return entry;\n}\n\n/** MAJOR.MINOR, both non-negative integers. */\nconst VERSION = /^(\\d+)\\.(\\d+)$/;\n/** `^MAJOR.MINOR` -- the caret is required: an entry names what it needs, never a bare number. */\nconst RANGE = /^\\^(\\d+)\\.(\\d+)$/;\n\n/**\n * Whether a caret range over MAJOR.MINOR admits a version: same major, minor\n * at least the range's. Malformed input admits nothing -- a range that cannot\n * be read must refuse, never pass.\n */\nexport function satisfiesRange(range: string, version: string): boolean {\n const r = RANGE.exec(range);\n const v = VERSION.exec(version);\n if (!r || !v) return false;\n return Number(r[1]) === Number(v[1]) && Number(v[2]) >= Number(r[2]);\n}\n\n/** The refusal an assembly raises: one message per collision, all of them, by name. */\nexport class ConsoleAssemblyError extends Error {\n constructor(readonly problems: readonly string[]) {\n super(`console assembly refused:\\n ${problems.join('\\n ')}`);\n this.name = 'ConsoleAssemblyError';\n }\n}\n\n/**\n * The checks every assembly runs, in one place so the build-time spec, the\n * bootstrap and a future install-time loader agree: entry ranges against\n * this contract, and the collision rules of the six lists. Returns the\n * entries unchanged when nothing is wrong.\n */\nexport function assembleConsole(entries: readonly ConsoleEntry[]): readonly ConsoleEntry[] {\n const problems: string[] = [];\n const seenModules = new Map<string, number>();\n const paths = new Map<string, string>();\n const bindings = new Map<string, string>();\n const topbar = new Map<string, string>();\n const overlays = new Map<string, string>();\n const panels = new Map<string, string>();\n const states = new Map<Type<unknown>, string>();\n const ports = new Map<InjectionToken<unknown>, string>();\n\n const claim = <K>(map: Map<K, string>, key: K, owner: string, what: string): void => {\n const prev = map.get(key);\n if (prev !== undefined && prev !== owner) {\n problems.push(`${what} '${String(key)}' is claimed by modules '${prev}' and '${owner}'`);\n } else {\n map.set(key, owner);\n }\n };\n\n for (const e of entries) {\n if (!satisfiesRange(e.range, CONSOLE_CONTRACT.version)) {\n problems.push(`module '${e.module}' offers console ${e.range}; this host implements console ${CONSOLE_CONTRACT.version} -- refused`);\n }\n seenModules.set(e.module, (seenModules.get(e.module) ?? 0) + 1);\n\n for (const r of e.routes ?? []) claim(paths, r.path, e.module, 'route path');\n for (const b of e.bindings ?? []) claim(bindings, b.name, e.module, 'binding');\n for (const t of e.topbar ?? []) claim(topbar, t.id, e.module, 'top-bar item');\n for (const o of e.overlays ?? []) claim(overlays, o.id, e.module, 'overlay');\n for (const p of e.panels ?? []) claim(panels, p.id, e.module, 'panel');\n for (const s of e.states ?? []) claim(states, s, e.module, 'state');\n for (const p of e.providers ?? []) {\n if (isPortProvision(p)) claim(ports, p.port, e.module, 'port');\n }\n }\n for (const [module, n] of seenModules) {\n if (n > 1) problems.push(`module '${module}' has ${n} entries; one entry per module`);\n }\n\n if (problems.length) throw new ConsoleAssemblyError(problems);\n return entries;\n}\n","import {\n computed,\n inject,\n Injectable,\n InjectionToken,\n makeEnvironmentProviders,\n provideAppInitializer,\n type EnvironmentProviders,\n type Provider,\n type Type,\n} from '@angular/core';\nimport type { CanMatchFn, Route, Routes } from '@angular/router';\nimport { provideStore, Store } from '@ngxs/store';\nimport { map, take, type Observable } from 'rxjs';\nimport type { ApiManifest } from '../../api/api-manifest.types';\nimport { AppInitService } from '../../bootstrap/app-init.service';\nimport { ComponentRegistry } from '../../navi-graph/component-registry';\nimport { AppConfigState } from '../../state/app-config.state';\nimport {\n assembleConsole,\n CONSOLE_CONTRACT,\n isPortProvision,\n type ConsoleEntry,\n type ConsoleOverlay,\n type ConsolePanel,\n type ConsoleTopbarItem,\n} from './console-v1';\n\n/**\n * The host side of `console@1`: what a theme calls to turn the assembled\n * entries into routes, providers and the lists its shell renders.\n *\n * Activation is the backend's: `GET /app-config` carries `ui.modules`, the\n * modules whose console entry the installer matched against the theme's\n * declaration. A module in the bundle but not in that list is dormant -- no\n * route matches, no tile, no overlay; a module in the list but not in the\n * bundle is reported by name ({@link ConsoleActivation.missing}), never\n * silently absent.\n */\n\n/** Every entry this build assembled, after {@link assembleConsole}'s checks. */\nexport const CONSOLE_ENTRIES = new InjectionToken<readonly ConsoleEntry[]>('coolms.console.entries');\n\n/**\n * The port a dock panel talks back through: the layout provides one per\n * panel it mounts, so the panel never needs an output the slot cannot carry.\n */\nexport abstract class ConsolePanelHost {\n abstract close(): void;\n abstract maximize(on: boolean): void;\n}\n\n/** The console modules the manifest says are installed here. */\nexport function installedConsoleModules(manifest: ApiManifest | null): ReadonlySet<string> {\n return new Set(\n (manifest?.ui?.modules ?? [])\n .filter(m => m.contract === CONSOLE_CONTRACT.name)\n .map(m => m.module),\n );\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ConsoleActivation {\n private readonly entries = inject(CONSOLE_ENTRIES, { optional: true }) ?? [];\n private readonly manifest = inject(Store).selectSignal(AppConfigState.manifest);\n\n readonly installed = computed(() => installedConsoleModules(this.manifest()));\n\n /** The entries of installed modules, in assembly order. */\n readonly active = computed(() => this.entries.filter(e => this.installed().has(e.module)));\n\n readonly topbar = computed<readonly ConsoleTopbarItem[]>(() =>\n this.active().flatMap(e => e.topbar ?? [])\n .slice()\n .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)),\n );\n readonly overlays = computed<readonly ConsoleOverlay[]>(() => this.active().flatMap(e => e.overlays ?? []));\n readonly panels = computed<readonly ConsolePanel[]>(() => this.active().flatMap(e => e.panels ?? []));\n\n /** Modules the manifest names that this build has no entry for: what a diagnostics page shows by name. */\n readonly missing = computed(() =>\n [...this.installed()].filter(m => !this.entries.some(e => e.module === m)).sort(),\n );\n\n isInstalled(module: string): boolean {\n return this.installed().has(module);\n }\n}\n\n/**\n * `canMatch` for a module's mount: the route exists only where the module is\n * installed. Answered once the manifest is in: on a cold deep link the router\n * recognises the URL while the initializer is still fetching `/theme/config`\n * (the two run as parallel initializers), and a synchronous read then found\n * no module installed, so every pasted module URL fell to the wildcard and\n * landed on the dashboard (measured 2026-09-22: no chunk requested for the\n * mount, the dashboard's instead). `ready$` is what the auth guard waits on\n * too, and it fires whether or not the config fetch succeeded.\n */\nexport function consoleModuleInstalled(module: string): CanMatchFn {\n return (): Observable<boolean> => {\n const activation = inject(ConsoleActivation);\n return inject(AppInitService).ready$.pipe(take(1), map(() => activation.isInstalled(module)));\n };\n}\n\n/**\n * The children a theme mounts under its authenticated layout for the\n * assembled entries: one `Route` per mount, lazy as written, `data` from the\n * entry's nav hint, `canMatch` from the manifest. The theme keeps its own\n * shape around them (login, the layout, `canActivate: [authGuard]`, the\n * wildcard).\n */\nexport function consoleChildren(entries: readonly ConsoleEntry[]): Routes {\n const routes: Routes = [];\n for (const e of assembleConsole(entries)) {\n for (const m of e.routes ?? []) {\n if ('redirectTo' in m) {\n routes.push({ path: m.path, redirectTo: m.redirectTo, pathMatch: 'full' });\n continue;\n }\n const route: Route = {\n path: m.path,\n canMatch: [consoleModuleInstalled(e.module)],\n ...(m.nav ? { data: { ...m.nav } } : {}),\n ...(m.providers ? { providers: [...m.providers] } : {}),\n };\n if ('children' in m) {\n route.loadChildren = m.children;\n } else {\n route.loadComponent = m.component;\n }\n routes.push(route);\n }\n }\n return routes;\n}\n\n/**\n * The providers a theme adds for the assembled entries: the store with the\n * host's states and every module state, the registry bindings (an\n * initializer, so they exist before the first render), every module\n * provision, and {@link CONSOLE_ENTRIES} for the shell. Place it AFTER the\n * library providers a module's provisions extend (the editor bridge, the\n * PDF viewer), as the hand-written lines were.\n */\nexport function provideConsole(\n entries: readonly ConsoleEntry[],\n options: { readonly hostStates: readonly Type<unknown>[] },\n): EnvironmentProviders {\n const checked = assembleConsole(entries);\n const providers: (Provider | EnvironmentProviders)[] = [\n { provide: CONSOLE_ENTRIES, useValue: checked },\n provideStore([...options.hostStates, ...checked.flatMap(e => e.states ?? [])]),\n provideAppInitializer(() => {\n const registry = inject(ComponentRegistry);\n for (const e of checked) {\n for (const b of e.bindings ?? []) registry.register(b.name, b.component);\n }\n }),\n ];\n for (const e of checked) {\n for (const p of e.providers ?? []) {\n providers.push(isPortProvision(p) ? { provide: p.port, useExisting: p.useExisting } : p);\n }\n }\n return makeEnvironmentProviders(providers);\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * The one fact the section interceptor needs: which site the operator is\n * currently working in.\n *\n * `core` is auth, config, errors and interceptors -- the layer everything else\n * is built on -- so it must not import a feature. The obvious-looking fix,\n * moving `SectionState` into core, is the wrong one: sections are a CMS domain\n * concept, and hoisting domain state into the base layer trades one bad\n * direction for another. So core declares the little it needs and the module\n * that owns the state provides it, bound in the composition root.\n *\n * Read SYNCHRONOUSLY on purpose. An interceptor decides whether to stamp a\n * header while building the request; it cannot wait on a stream.\n */\nexport interface CurrentSectionPort {\n /** Slug of the active section, or null when the operator picked none. */\n currentSlug(): string | null;\n}\n\n/**\n * Optional by design: an application assembled without a Sections module binds\n * nothing and simply sends no `X-CoolMS-Section` header, which is exactly what\n * a single-site install wants.\n */\nexport const CURRENT_SECTION = new InjectionToken<CurrentSectionPort>(\n 'coolms.current-section',\n);\n","import { inject } from '@angular/core';\nimport { type HttpInterceptorFn } from '@angular/common/http';\nimport { CURRENT_SECTION } from '../section/current-section.port';\n\n/**\n * Phase H7 -- admin Site Selector.\n *\n * Stamps `X-CoolMS-Section: <slug>` on outgoing `/api/v1/*` requests when\n * the admin has picked a section from the Site Selector dropdown. The backend\n * honours it for AUTHENTICATED requests only -- anonymous traffic cannot pivot\n * the active section.\n *\n * What the header actually reaches, counted rather than intended: exactly two\n * readers act on it, `CreatePageProcessor` and `CreateCollectionProcessor`,\n * each choosing WHICH SITE a newly created page or collection lands in -- and,\n * for a page, that section's default locale and slug-naming policy. Nothing\n * else is scoped by it. Media spaces list every active section regardless, and\n * every other reader of `_coolms_section` sits on the public SSR surface,\n * which resolves from host+path and never sees this header.\n *\n * This block used to say the backend \"scopes site-specific API providers\n * (pages, media spaces, etc.)\". That described where was heading --\n * H4/H5, multisite Document and Media, never landed -- not what it does. Left\n * recorded because a docstring naming an intended destination reads exactly\n * like one naming a shipped feature, and this one misdescribed the control for\n * three months.\n *\n * Skipped:\n * - non-API URLs (no section context needed for SSR/static)\n * - `/auth/*` endpoints (login/refresh are pre-session; the header would\n * be ignored by the backend anyway, but skipping keeps wire traffic clean)\n * - requests that already carry the header (programmatic overrides win)\n */\nexport const sectionInterceptor: HttpInterceptorFn = (req, next) => {\n if (!shouldStamp(req.url)) {\n return next(req);\n }\n if (req.headers.has('X-CoolMS-Section')) {\n return next(req);\n }\n\n // Optional: no Sections module installed means no section to stamp.\n const slug = inject(CURRENT_SECTION, { optional: true })?.currentSlug() ?? null;\n if (!slug) return next(req);\n\n return next(req.clone({ setHeaders: { 'X-CoolMS-Section': slug } }));\n};\n\nfunction shouldStamp(url: string): boolean {\n // Strip protocol/host so absolute and relative URLs are treated alike.\n const path = url.replace(/^https?:\\/\\/[^/]+/, '');\n if (!path.startsWith('/api/v1/')) return false;\n if (path.startsWith('/api/v1/auth/')) return false;\n return true;\n}\n"],"mappings":"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"}
|