@coolms/core-angular 2.0.0-alpha.2

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.
@@ -0,0 +1,1415 @@
1
+ import * as rxjs from 'rxjs';
2
+ import { Observable } from 'rxjs';
3
+ import * as i0 from '@angular/core';
4
+ import { Type, InjectionToken } from '@angular/core';
5
+ import { StateContext } from '@ngxs/store';
6
+ import { CanActivateFn } from '@angular/router';
7
+ import { HttpContextToken, HttpInterceptorFn } from '@angular/common/http';
8
+
9
+ interface AuthApiManifest {
10
+ readonly login: string;
11
+ readonly logout: string;
12
+ readonly refresh: string;
13
+ readonly register: string;
14
+ readonly me: string;
15
+ readonly usersApi?: string;
16
+ readonly groupsApi?: string;
17
+ readonly preferencesUrl?: string;
18
+ readonly groupFormId?: string | null;
19
+ readonly userFormId?: string | null;
20
+ }
21
+ interface SectionApiManifest {
22
+ readonly list: string;
23
+ readonly create: string;
24
+ readonly item: string;
25
+ readonly update: string;
26
+ readonly delete: string;
27
+ readonly formId?: string;
28
+ readonly apply?: string;
29
+ }
30
+ interface NaviApiManifest {
31
+ readonly treesList: string;
32
+ readonly treesCreate: string;
33
+ readonly treesItem: string;
34
+ readonly nodesList: string;
35
+ readonly nodesCreate: string;
36
+ readonly nodesItem: string;
37
+ readonly nodesReorder: string;
38
+ readonly nodesMove: string;
39
+ readonly nodesByTree: string;
40
+ readonly treesFormId?: string;
41
+ readonly nodesFormId?: string;
42
+ readonly adminNavGraph?: string;
43
+ readonly topbarNavGraph?: string;
44
+ readonly toolbarVfsGraph?: string;
45
+ readonly graphBySlug?: string;
46
+ }
47
+ interface ContentApiManifest {
48
+ readonly variantPublishUrl: string;
49
+ readonly pageSpacesUrl?: string;
50
+ readonly pageTypesUrl?: string;
51
+ }
52
+ interface DataGridApiManifest {
53
+ readonly configBase: string;
54
+ }
55
+ interface TerminalApiManifest {
56
+ readonly executeUrl: string;
57
+ readonly completeUrl: string;
58
+ }
59
+ interface MediaApiManifest {
60
+ readonly listUrl: string;
61
+ readonly itemUrl: string;
62
+ readonly permissionsUrl: string;
63
+ readonly regenerateUrl: string;
64
+ readonly resizeUrl: string;
65
+ readonly collectionsUrl: string;
66
+ readonly collectionsCreateUrl: string;
67
+ readonly collectionPermissionsUrl: string;
68
+ readonly moveUrl?: string;
69
+ readonly spacesUrl?: string;
70
+ }
71
+ interface VfsApiManifest {
72
+ readonly fileContentUrl: string;
73
+ readonly binaryWriteUrl: string;
74
+ }
75
+ /**
76
+ * Document module URL section. Today the only entry is `spacesUrl`;
77
+ * the struct is reserved so future Document-module URLs can land
78
+ * here without a new manifest section.
79
+ */
80
+ interface DocumentApiManifest {
81
+ readonly spacesUrl?: string;
82
+ }
83
+ interface DynamicEntityApiManifest {
84
+ readonly typesUrl: string;
85
+ readonly fieldDefinitionsUrl: string;
86
+ readonly fieldDefinitionUrl: string;
87
+ readonly fieldDefinitionsReorderUrl: string;
88
+ readonly toolbarNaviGraphUrl: string;
89
+ readonly constraintsUrl: string;
90
+ readonly recordsUrl: string;
91
+ readonly recordUrl: string;
92
+ readonly recordsByTypeUrl: string;
93
+ readonly typeUrl: string;
94
+ readonly typesCreateUrl: string;
95
+ readonly formTypesUrl?: string;
96
+ readonly typeByAliasUrl?: string;
97
+ }
98
+ interface DomainExplorerApiManifest {
99
+ readonly entitiesUrl: string;
100
+ readonly entityUrl: string;
101
+ readonly toolbarNaviGraphUrl: string;
102
+ readonly domainExplorerToolbarUrl: string;
103
+ readonly entityFieldsDataGridUrl?: string;
104
+ readonly typeFieldsDataGridUrl?: string;
105
+ }
106
+ interface IdentityApiManifest {
107
+ readonly usersUrl: string;
108
+ readonly userUrl: string;
109
+ readonly groupsUrl: string;
110
+ readonly groupUrl: string;
111
+ readonly meUrl: string;
112
+ readonly assignGroupsUrl: string;
113
+ readonly avatarUploadUrl: string;
114
+ readonly settingsUrl: string;
115
+ readonly settingsSectionsUrl: string;
116
+ readonly settingsSectionUrl: string;
117
+ readonly colorUrl: string;
118
+ readonly rolesUrl?: string;
119
+ }
120
+ /**
121
+ * Editor sub-manifest emitted by the backend's Editor module. The
122
+ * frontend bridge (`@coolms/editor-angular`) consumes this through the
123
+ * `EDITOR_MANIFEST_PROVIDER` token.
124
+ *
125
+ * schemaVersion 2 (sub-prompt D) replaced the editorId axis with a profile
126
+ * axis: `manifest.editor.profiles[name]` returns a slice with the
127
+ * pre-resolved contributor list AND the storage allow-list a sanitizer
128
+ * enforces on save.
129
+ */
130
+ interface EditorApiManifestEntry {
131
+ readonly id: string;
132
+ readonly group: string;
133
+ readonly priority: number;
134
+ readonly icon: string;
135
+ readonly label: string;
136
+ readonly actionType: string;
137
+ readonly actionParams: Readonly<Record<string, unknown>>;
138
+ readonly extensions: ReadonlyArray<string>;
139
+ readonly stateKeys: ReadonlyArray<string>;
140
+ readonly shortcut?: string | null;
141
+ readonly slashable?: boolean;
142
+ readonly keywords?: ReadonlyArray<string>;
143
+ }
144
+ interface EditorApiManifestProfile {
145
+ readonly contributors: ReadonlyArray<EditorApiManifestEntry>;
146
+ readonly allowedWidgets: ReadonlyArray<string>;
147
+ }
148
+ interface EditorApiManifest {
149
+ readonly schemaVersion: number;
150
+ readonly profiles: Readonly<Record<string, EditorApiManifestProfile>>;
151
+ }
152
+ /**
153
+ * Viewer manifest emitted by the backend's Document module.
154
+ * Each viewer declares the MIME types / extensions it handles, the
155
+ * Angular component selector to dispatch into, and one config blob per
156
+ * profile. Profile config is open-ended — every viewer interprets the
157
+ * keys it understands and ignores the rest.
158
+ */
159
+ interface ViewerProfileApiManifest {
160
+ readonly key: string;
161
+ readonly config: Readonly<Record<string, unknown>>;
162
+ }
163
+ interface ViewerDefinitionApiManifest {
164
+ readonly key: string;
165
+ readonly mimeTypes: ReadonlyArray<string>;
166
+ readonly extensions: ReadonlyArray<string>;
167
+ readonly component: string;
168
+ readonly profiles: Readonly<Record<string, ViewerProfileApiManifest>>;
169
+ }
170
+ interface ViewerApiManifest {
171
+ readonly schemaVersion: number;
172
+ readonly viewers: Readonly<Record<string, ViewerDefinitionApiManifest>>;
173
+ }
174
+ /**
175
+ * Platform-wide default user-facing settings. The
176
+ * fallback floor for anonymous / pre-login rendering: timezone, date /
177
+ * time format (CLDR tokens) and week start come from the deployment's
178
+ * config, not an FE-hardcoded Western default. `locale` is the default
179
+ * locale, previously absent from the manifest.
180
+ */
181
+ interface PlatformDefaults {
182
+ readonly locale: string;
183
+ readonly timezone: string;
184
+ readonly dateFormat: string;
185
+ readonly timeFormat: '12h' | '24h';
186
+ readonly weekStart: 'monday' | 'sunday';
187
+ /**
188
+ * Deployment brand accent as `#rrggbb`, or null/absent to keep the
189
+ * stylesheet's own. Sits BELOW a user's personal accentColor.
190
+ */
191
+ readonly accentColor?: string | null;
192
+ }
193
+ interface ApiManifest {
194
+ readonly apiBase: string;
195
+ readonly configBase?: string;
196
+ readonly auth?: AuthApiManifest;
197
+ readonly identity?: IdentityApiManifest;
198
+ readonly sections?: SectionApiManifest;
199
+ readonly navi?: NaviApiManifest;
200
+ readonly content?: ContentApiManifest;
201
+ readonly dataGrid?: DataGridApiManifest;
202
+ readonly terminal?: TerminalApiManifest;
203
+ readonly media?: MediaApiManifest;
204
+ readonly document?: DocumentApiManifest;
205
+ readonly vfs?: VfsApiManifest;
206
+ readonly dynamicEntity?: DynamicEntityApiManifest;
207
+ readonly domainExplorer?: DomainExplorerApiManifest;
208
+ readonly editor?: EditorApiManifest;
209
+ readonly viewers?: ViewerApiManifest;
210
+ readonly supportedLocales?: Array<{
211
+ code: string;
212
+ label: string;
213
+ }>;
214
+ readonly platformDefaults?: PlatformDefaults;
215
+ }
216
+ interface ThemeConfigResponse {
217
+ readonly slug?: string | null;
218
+ readonly feStack?: string | null;
219
+ readonly spaFramework?: string | null;
220
+ readonly assetsUrl?: string | null;
221
+ readonly manifest: ApiManifest;
222
+ }
223
+ /**
224
+ * Replaces {param} tokens in a URL pattern with encoded values.
225
+ *
226
+ * @example
227
+ * resolvePattern(manifest.sections!.item, { id: '123' })
228
+ * // -> '/api/v1/sections/123'
229
+ */
230
+ declare function resolvePattern(pattern: string, params: Record<string, string>): string;
231
+
232
+ /**
233
+ * Shapes the user-preference store persists.
234
+ *
235
+ * {@link UserPreferencesService} owns ONE store (`coolms_ui_prefs`) with
236
+ * several namespaces -- grids, panels, page state, nav, terminal -- so these
237
+ * shapes are core's and the surfaces that read them are consumers. The datagrid
238
+ * is simply the namespace with the richest shape.
239
+ *
240
+ * `shared/datagrid/datagrid-preferences.types` re-exports them, so the kit
241
+ * keeps its own local path and nothing that reads a grid preference had to
242
+ * change.
243
+ */
244
+ interface DataGridPreference {
245
+ columns?: string[];
246
+ columnWidths?: Record<string, number>;
247
+ }
248
+ type DataGridPreferences = Record<string, DataGridPreference>;
249
+
250
+ /**
251
+ * Session and current-user DTOs.
252
+ *
253
+ * They live in core because core runs the session: the NGXS auth state, the
254
+ * refresh coordinator and the cross-tab sync are all here, and all three name
255
+ * these shapes. They used to be declared inside the admin's `ApiService`, which
256
+ * meant core imported a 2410-line client to read two interfaces.
257
+ *
258
+ * `ApiService` re-exports them from its own module, so the feature files that
259
+ * import `UserDto` from there are untouched.
260
+ */
261
+ interface TokenResponse {
262
+ accessToken: string;
263
+ refreshToken: string;
264
+ expiresAt: string;
265
+ user?: UserDto;
266
+ }
267
+ interface UserDto {
268
+ id: string;
269
+ email: string;
270
+ identifier?: string;
271
+ roles: string[];
272
+ uiPrefs?: DataGridPreferences;
273
+ avatarUrl?: string | null;
274
+ firstName?: string | null;
275
+ lastName?: string | null;
276
+ fullName?: string;
277
+ }
278
+
279
+ type FieldType = 'text' | 'email' | 'password' | 'number' | 'textarea' | 'select' | 'toggle' | 'date' | 'time' | 'hidden' | 'relation' | 'subform' | 'token-pattern' | 'richtext' | 'localizedText' | 'localizedTextarea' | 'optionsEditor' | 'intlPhone' | 'color';
280
+ type DataSourceType = 'static' | 'enum' | 'api' | 'repo';
281
+ type VisibilityOperator = 'eq' | 'ne' | 'in' | 'notIn' | 'empty' | 'notEmpty';
282
+ interface VisibilityCondition {
283
+ readonly field: string;
284
+ readonly operator: VisibilityOperator;
285
+ readonly value?: unknown;
286
+ }
287
+ type DataSourceWidget = 'select' | 'select-search' | 'select-tree' | 'token-input' | 'media-picker';
288
+ type DataSourceLoading = 'eager' | 'lazy';
289
+ interface DataSourceOption {
290
+ readonly value: unknown;
291
+ readonly label: string;
292
+ readonly parentId?: string | null;
293
+ }
294
+ interface DataSourceDefinition {
295
+ readonly type: DataSourceType;
296
+ readonly bindValue: string;
297
+ readonly bindLabel: string;
298
+ readonly multiple: boolean;
299
+ readonly maxItems?: number;
300
+ readonly options?: ReadonlyArray<DataSourceOption>;
301
+ readonly url?: string;
302
+ readonly widget: DataSourceWidget;
303
+ readonly loading: DataSourceLoading;
304
+ /** Free-form widget-specific configuration block populated from YAML
305
+ * `dataSource.widgetOptions`. Used by media-picker for bindTarget /
306
+ * display / accept / recentlyUsed / hoverPreview, and reserved for
307
+ * future custom widgets. Kept separate from `options` (which is the
308
+ * static-option list) to avoid key collisions. */
309
+ readonly widgetOptions?: Readonly<Record<string, unknown>>;
310
+ }
311
+ interface ValidatorDefinition {
312
+ readonly type: string;
313
+ readonly value?: unknown;
314
+ readonly message?: string;
315
+ }
316
+ interface RelationDefinition {
317
+ readonly cardinality: 'one' | 'many';
318
+ readonly maxItems?: number;
319
+ readonly targetFormId?: string;
320
+ readonly dataSource?: DataSourceDefinition;
321
+ }
322
+ interface SubFormDefinition {
323
+ readonly formId: string;
324
+ readonly relation: 'one' | 'many';
325
+ readonly maxItems?: number;
326
+ }
327
+ interface FieldSecurityPolicy {
328
+ readonly read: ReadonlyArray<string>;
329
+ readonly write: ReadonlyArray<string>;
330
+ }
331
+ interface FieldItem {
332
+ readonly alias: string;
333
+ readonly type: FieldType;
334
+ readonly label: string;
335
+ readonly placeholder?: string;
336
+ readonly hint?: string;
337
+ readonly required: boolean;
338
+ readonly readonly: boolean;
339
+ readonly locked: boolean;
340
+ readonly private: boolean;
341
+ readonly validators: ReadonlyArray<ValidatorDefinition>;
342
+ readonly dataSource?: DataSourceDefinition;
343
+ readonly relation?: RelationDefinition;
344
+ readonly subForm?: SubFormDefinition;
345
+ readonly separators?: ReadonlyArray<string>;
346
+ readonly security?: FieldSecurityPolicy | null;
347
+ readonly autocomplete?: string;
348
+ }
349
+ interface LayoutNode {
350
+ readonly type: 'group' | 'grid' | 'column' | 'tabs' | 'tab' | (string & {});
351
+ readonly name?: string;
352
+ readonly description?: string;
353
+ readonly compact?: boolean;
354
+ readonly width?: number;
355
+ readonly children?: ReadonlyArray<LayoutNode>;
356
+ readonly showWhen?: VisibilityCondition;
357
+ }
358
+ type FormActionType = 'submit' | 'button' | 'reset';
359
+ type FormActionVariant = 'primary' | 'secondary' | 'danger' | 'link';
360
+ /** Terminal form button (Submit / Cancel / …) — mirrors PHP FormAction.
361
+ * Resolved by the backend (declared `formOptions.actions`, else lifted from
362
+ * legacy submit fields, else a default Submit). `label` is already translated. */
363
+ interface FormAction {
364
+ readonly type: FormActionType;
365
+ readonly label: string;
366
+ readonly name: string;
367
+ readonly variant: FormActionVariant;
368
+ }
369
+ interface FormRenderDefinition {
370
+ readonly id: string;
371
+ readonly context: 'create' | 'edit';
372
+ readonly layout: ReadonlyArray<LayoutNode>;
373
+ readonly items: ReadonlyArray<FieldItem>;
374
+ readonly actions: ReadonlyArray<FormAction>;
375
+ readonly action?: string;
376
+ }
377
+
378
+ /**
379
+ * The collection envelope API Platform wraps every list response in.
380
+ *
381
+ * It belongs to the client runtime rather than to any one screen: it is the
382
+ * shape of EVERY paginated response the CoolMS API emits, so a feature that
383
+ * lists anything needs it, and a UI kit that lists anything must not have to
384
+ * reach into the application to describe it.
385
+ *
386
+ * **The keys arrive WITHOUT the `hydra:` prefix.** The vocabulary is mapped in
387
+ * `@context`, so the wire carries `member` and `totalItems` rather than
388
+ * `hydra:member` and `hydra:totalItems`.
389
+ *
390
+ * Three document services in the admin declare their own version of this
391
+ * interface accepting BOTH spellings, every field optional. That is a
392
+ * different claim about the same wire, not a duplicate of this one, and
393
+ * whichever is wrong is wrong silently -- an optional `member` makes every
394
+ * call site handle an absence that may never happen, and a required one
395
+ * crashes if it does. Settle it by calling the endpoint, not by merging the
396
+ * types.
397
+ */
398
+ interface HydraView {
399
+ '@id': string;
400
+ 'next'?: string;
401
+ 'previous'?: string;
402
+ 'last'?: string;
403
+ }
404
+ interface HydraCollection<T> {
405
+ 'member': T[];
406
+ 'totalItems': number;
407
+ 'view'?: HydraView;
408
+ }
409
+
410
+ /**
411
+ * The identity endpoints core owns: the session lifecycle, plus the settings
412
+ * read at boot.
413
+ *
414
+ * Carved out of the admin's `ApiService` -- 2410 lines, 118 methods and 89 DTOs
415
+ * spanning calendars, documents, calls, VFS and the rest. Core cannot depend on
416
+ * that and still ship beneath the UI kit: it would drag every module's wire
417
+ * types down with it. Core called exactly FOUR of those methods.
418
+ *
419
+ * `logout` is the one addition, and not for symmetry: the auth lifecycle lives
420
+ * here (state, refresh coordinator, cross-tab sync), so a core package that
421
+ * could sign in but not out would be incomplete.
422
+ *
423
+ * `ApiService` keeps its own identical signatures and delegates here, so none
424
+ * of the 94 feature files calling it changed.
425
+ */
426
+ declare class IdentityApiClient {
427
+ private readonly http;
428
+ private readonly store;
429
+ /** URLs come from the boot manifest, which core already owns. */
430
+ private get manifest();
431
+ login(identifier: string, password: string): Observable<TokenResponse>;
432
+ refresh(refreshToken: string): Observable<TokenResponse>;
433
+ logout(): Observable<void>;
434
+ me(): Observable<UserDto>;
435
+ getSettings(): Observable<Record<string, Record<string, unknown>>>;
436
+ static ɵfac: i0.ɵɵFactoryDeclaration<IdentityApiClient, never>;
437
+ static ɵprov: i0.ɵɵInjectableDeclaration<IdentityApiClient>;
438
+ }
439
+
440
+ /**
441
+ * Response of `POST /centrifugo/connection-token`: the JWT the realtime SDK
442
+ * opens its socket with, and what it needs to refresh before expiry.
443
+ */
444
+ interface CentrifugoConnectionTokenDto {
445
+ token: string;
446
+ expiresAt: number;
447
+ ttl: number;
448
+ wsUrl: string;
449
+ }
450
+ /**
451
+ * Response of `POST /centrifugo/subscription-token`: a per-channel JWT. The
452
+ * SDK asks for one before subscribing to a private namespace, so this is
453
+ * fetched per subscription rather than once per connection.
454
+ */
455
+ interface CentrifugoSubscriptionTokenDto {
456
+ channel: string;
457
+ token: string;
458
+ expiresAt: number;
459
+ ttl: number;
460
+ }
461
+ /**
462
+ * The two realtime endpoints, carved out for the same reason the identity ones
463
+ * were: the UI kit's realtime client needed exactly these two of `ApiService`'s
464
+ * 117 methods, and depending on the whole thing would have pulled the entire
465
+ * application's wire vocabulary underneath the kit.
466
+ *
467
+ * They belong in core rather than in the kit because both answers are derived
468
+ * from things core already owns -- the boot manifest for the URL, and the auth
469
+ * interceptor for the bearer token that authorises the request. A token is
470
+ * session state, not a widget.
471
+ *
472
+ * `ApiService` keeps its identical signatures and delegates here, so nothing
473
+ * calling it had to change.
474
+ */
475
+ declare class RealtimeTokenClient {
476
+ private readonly http;
477
+ private readonly store;
478
+ private get manifest();
479
+ connectionToken(): Observable<CentrifugoConnectionTokenDto>;
480
+ subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto>;
481
+ static ɵfac: i0.ɵɵFactoryDeclaration<RealtimeTokenClient, never>;
482
+ static ɵprov: i0.ɵɵInjectableDeclaration<RealtimeTokenClient>;
483
+ }
484
+
485
+ declare class Login {
486
+ identifier: string;
487
+ password: string;
488
+ static readonly type = "[Auth] Login";
489
+ constructor(identifier: string, password: string);
490
+ }
491
+ declare class Logout {
492
+ static readonly type = "[Auth] Logout";
493
+ }
494
+ declare class RestoreSession {
495
+ static readonly type = "[Auth] Restore Session";
496
+ }
497
+ declare class SetTokens {
498
+ readonly response: TokenResponse;
499
+ static readonly type = "[Auth] Set Tokens";
500
+ constructor(response: TokenResponse);
501
+ }
502
+ /** Merge a partial update into the currently-stored user (e.g. after avatar change). */
503
+ declare class PatchCurrentUser {
504
+ readonly patch: Partial<UserDto>;
505
+ static readonly type = "[Auth] Patch Current User";
506
+ constructor(patch: Partial<UserDto>);
507
+ }
508
+
509
+ interface AuthStateModel {
510
+ accessToken: string | null;
511
+ refreshToken: string | null;
512
+ expiresAt: string | null;
513
+ user: UserDto | null;
514
+ }
515
+ declare class AuthState {
516
+ private readonly api;
517
+ private readonly prefs;
518
+ login(ctx: StateContext<AuthStateModel>, { identifier, password }: Login): rxjs.Observable<UserDto | null>;
519
+ logout(ctx: StateContext<AuthStateModel>): void;
520
+ restore(ctx: StateContext<AuthStateModel>): void;
521
+ setTokens(ctx: StateContext<AuthStateModel>, { response }: SetTokens): void;
522
+ patchCurrentUser(ctx: StateContext<AuthStateModel>, { patch }: PatchCurrentUser): void;
523
+ static isAuthenticated(state: AuthStateModel): boolean;
524
+ static accessToken(state: AuthStateModel): string | null;
525
+ static refreshToken(state: AuthStateModel): string | null;
526
+ /**
527
+ * Sub-phase Y -- expose the access-token expiry timestamp so
528
+ * preemptive-refresh callers (e.g., Centrifuge getToken
529
+ * callbacks) can decide whether to rotate before issuing the
530
+ * next protected request, instead of relying on the
531
+ * interceptor's reactive 401-retry path.
532
+ */
533
+ static expiresAt(state: AuthStateModel): string | null;
534
+ static currentUser(state: AuthStateModel): UserDto | null;
535
+ static ɵfac: i0.ɵɵFactoryDeclaration<AuthState, never>;
536
+ static ɵprov: i0.ɵɵInjectableDeclaration<AuthState>;
537
+ }
538
+
539
+ /**
540
+ * Protects routes that require authentication.
541
+ *
542
+ * The guard is intentionally ASYNC: it waits for AppInitService.ready$ before
543
+ * evaluating auth state. Without this, Angular's router can evaluate
544
+ * isAuthenticated while APP_INITIALIZER is still in-flight — RestoreSession
545
+ * will have placed a non-null (but expired) token in state, causing the guard
546
+ * to pass even though the subsequent config/refresh requests will fail and
547
+ * dispatch Logout. Waiting for ready$ guarantees the auth state is settled.
548
+ */
549
+ declare const authGuard: CanActivateFn;
550
+
551
+ /**
552
+ * Guards the `/login` route -- redirects already-authenticated
553
+ * callers to admin home so a stale tab parked on `/login` cannot
554
+ * remain mounted while another tab is logged in. When Tab A logs
555
+ * in fresh, Tab B's `RestoreSession` (driven by APP_INITIALIZER)
556
+ * reads the new token from shared `localStorage` and populates
557
+ * `AuthState`; this guard then sees `isAuthenticated === true`
558
+ * and routes Tab B to `/`.
559
+ *
560
+ * Same `ready$` wait as `authGuard` -- ensures `RestoreSession`
561
+ * has run before the snapshot is read; otherwise a fresh F5 on
562
+ * `/login` with a valid stored session would briefly evaluate
563
+ * as anonymous, allow `/login`, and only then have the token
564
+ * populated.
565
+ */
566
+ declare const loginPageGuard: CanActivateFn;
567
+
568
+ declare const BYPASS_AUTH: HttpContextToken<boolean>;
569
+ declare const authInterceptor: HttpInterceptorFn;
570
+
571
+ /**
572
+ * Two-layer refresh coordinator: an intra-tab `shareReplay(1)`
573
+ * mutex dedupes concurrent callers within one tab, and a
574
+ * cross-tab `navigator.locks` wrapper ensures only one tab in
575
+ * the browser issues the actual `POST /auth/refresh` at a time.
576
+ *
577
+ * Without the cross-tab layer, two tabs racing the same expired
578
+ * refresh-token both POST `/auth/refresh`; the backend's
579
+ * `isUsed` replay branch revokes every token for the user on
580
+ * the second arrival and the operator is kicked to /login.
581
+ *
582
+ * The Web Lock body re-reads the current access-token expiry from
583
+ * `AuthState` before issuing the HTTP call. When this tab waited
584
+ * behind another tab's refresh, that tab's `SetTokens` dispatch
585
+ * has already published fresh values into shared `localStorage`,
586
+ * `CrossTabAuthSyncService` has mirrored them into this tab's
587
+ * NGXS state, and the lock body simply returns the cached access
588
+ * token without another network round-trip.
589
+ *
590
+ * `Logout` fires only when the refresh endpoint returns 401
591
+ * (real auth failure). Transient errors (network blips, 5xx,
592
+ * CORS, timeouts) propagate without clearing the session so a
593
+ * momentary refresh failure does not kill a valid login.
594
+ */
595
+ declare class AuthRefreshCoordinator {
596
+ private readonly api;
597
+ private readonly store;
598
+ private readonly router;
599
+ private inFlight;
600
+ /**
601
+ * Return the in-flight refresh Observable, or spawn a new one
602
+ * with the supplied refresh-token value.
603
+ *
604
+ * Callers subscribe via `switchMap` / `firstValueFrom` to retry
605
+ * their original request with the resolved access token.
606
+ */
607
+ refresh(refreshToken: string): Observable<string>;
608
+ /**
609
+ * Run the refresh body inside `navigator.locks` when the API is
610
+ * available; degrade to direct invocation otherwise. The lock
611
+ * body checks for state already updated by a sibling tab before
612
+ * issuing its own HTTP call.
613
+ */
614
+ private refreshWithCrossTabLock;
615
+ /**
616
+ * Lock body. Re-reads `AuthState.expiresAt` first: if another
617
+ * tab published fresh tokens while we were queued, return the
618
+ * cached access token directly. Otherwise read the latest
619
+ * refresh-token value from state (it may have rotated while we
620
+ * waited) and issue the HTTP refresh.
621
+ */
622
+ private executeRefresh;
623
+ /**
624
+ * Return the cached access token when `AuthState.expiresAt` has
625
+ * more than `FRESH_TOKEN_GRACE_MS` milliseconds remaining --
626
+ * signalling that some other tab refreshed for us. Null
627
+ * otherwise.
628
+ */
629
+ private tryConsumeFreshState;
630
+ static ɵfac: i0.ɵɵFactoryDeclaration<AuthRefreshCoordinator, never>;
631
+ static ɵprov: i0.ɵɵInjectableDeclaration<AuthRefreshCoordinator>;
632
+ }
633
+
634
+ /**
635
+ * Bridges shared `localStorage` mutations into the NGXS auth
636
+ * state so a refresh executed in one tab silently propagates to
637
+ * every other open tab.
638
+ *
639
+ * The browser fires `storage` events in tabs OTHER than the one
640
+ * that mutated `localStorage`. So when Tab A's
641
+ * `AuthRefreshCoordinator` rotates the token pair and `SetTokens`
642
+ * persists the new values, every sibling tab receives an event
643
+ * here and dispatches `SetTokens` against its own NGXS state --
644
+ * no extra HTTP roundtrip, no extra refresh contention.
645
+ *
646
+ * Pairs with the `navigator.locks` cross-tab mutex in
647
+ * `AuthRefreshCoordinator`: locks ensure only one tab issues the
648
+ * refresh, this listener propagates the result to the rest.
649
+ *
650
+ * `Logout` from any tab clears `coolms_token` (newValue === null)
651
+ * and this listener mirrors that into the sibling tabs so the
652
+ * shell redirects to `/login` without waiting for the next
653
+ * protected request to 401.
654
+ *
655
+ * Started from `AppInitService.load()` so the listener is in
656
+ * place before any protected HTTP fires.
657
+ */
658
+ declare class CrossTabAuthSyncService {
659
+ private readonly store;
660
+ private listenerAttached;
661
+ start(): void;
662
+ private readonly handleStorageEvent;
663
+ static ɵfac: i0.ɵɵFactoryDeclaration<CrossTabAuthSyncService, never>;
664
+ static ɵprov: i0.ɵɵInjectableDeclaration<CrossTabAuthSyncService>;
665
+ }
666
+
667
+ declare class LoginComponent {
668
+ identifier: string;
669
+ password: string;
670
+ loading: boolean;
671
+ error: string | null;
672
+ private readonly store;
673
+ private readonly router;
674
+ submit(): void;
675
+ static ɵfac: i0.ɵɵFactoryDeclaration<LoginComponent, never>;
676
+ static ɵcmp: i0.ɵɵComponentDeclaration<LoginComponent, "coolms-admin-login", never, {}, {}, never, never, true, never>;
677
+ }
678
+
679
+ declare class AppInitService {
680
+ private readonly http;
681
+ private readonly store;
682
+ private readonly router;
683
+ private readonly location;
684
+ private readonly prefs;
685
+ private readonly crossTabSync;
686
+ private readonly refreshCoordinator;
687
+ /** The only hardcoded URL — everything else comes from the manifest. */
688
+ private readonly configUrl;
689
+ /**
690
+ * Emits (once) when load() has fully completed — tokens settled, manifest
691
+ * set (or swallowed on error). authGuard subscribes to this so it never
692
+ * evaluates isAuthenticated against a partially-restored stale token.
693
+ */
694
+ private readonly initComplete;
695
+ readonly ready$: rxjs.Observable<void>;
696
+ load(): Promise<void>;
697
+ /**
698
+ * Confirm the restored session is still alive, settling AuthState before the
699
+ * app unblocks. Runs OUTSIDE the auth interceptor (BYPASS_AUTH) because the
700
+ * interceptor queues behind ready$, which has not fired yet at this point.
701
+ *
702
+ * - access token accepted -> done, session alive;
703
+ * - access token rejected -> attempt ONE refresh (access tokens are short-lived,
704
+ * so an expired one with a valid refresh token must
705
+ * stay logged in). The coordinator dispatches Logout
706
+ * on a refresh 401 and SetTokens on success; a
707
+ * transient failure (5xx/network) is swallowed so a
708
+ * momentary blip never kills a valid session.
709
+ */
710
+ private validateSession;
711
+ /** GET an authed endpoint with an explicit Bearer header, bypassing the interceptor. */
712
+ private probe;
713
+ static ɵfac: i0.ɵɵFactoryDeclaration<AppInitService, never>;
714
+ static ɵprov: i0.ɵɵInjectableDeclaration<AppInitService>;
715
+ }
716
+
717
+ /**
718
+ * Layout config DTO -- the shape served from
719
+ * `GET /api/v1/config/layout/{id}`. Mirrors the YAML at
720
+ * `config/modules/{module}/layout/*.yaml`.
721
+ *
722
+ * The optional fields below are the page-level contract
723
+ * every admin SPA page resolves through. The base contract (id /
724
+ * template / title / icon / slots) is the legacy list shape and stays
725
+ * untouched. Existing 17 list YAMLs render identically when the new
726
+ * fields are absent.
727
+ *
728
+ * New optional fields:
729
+ * - `subtitle` one-line page description rendered under the title
730
+ * - `sections` ordered map of body sections, each with its own slot graph
731
+ * (replaces single-slot `slots:` for multi-section pages)
732
+ * - `headerActions` buttons displayed in the cms-page-header action bar
733
+ * - `permissions` hint for clients about who this page targets; real gating
734
+ * lives on the data APIs the slot components consume
735
+ * (the layout endpoint itself is public by design --
736
+ * layouts carry only structure, never data)
737
+ *
738
+ * `[key: string]: unknown` index signature stays so existing per-page
739
+ * keys (e.g. `media:library`'s `naviGraph:` slot shorthand) keep working
740
+ * without further interface changes.
741
+ */
742
+ interface LayoutHeaderAction {
743
+ id: string;
744
+ label?: string;
745
+ icon?: string;
746
+ title?: string;
747
+ primary?: boolean;
748
+ danger?: boolean;
749
+ disabled?: boolean;
750
+ /**
751
+ * Optional capability gate evaluated FE-side against runtime state
752
+ * (e.g. `administer` / `delete`) — the config declares WHICH actions
753
+ * exist + their gate; the FE decides if THIS user/row qualifies. Absent
754
+ * = always shown. Used by detail pages whose actions are config-driven
755
+ * (see `web:section-detail`).
756
+ *
757
+ * For a gate that depends on the RECORD rather than the user, prefer
758
+ * `showWhen` below: `requires` needs a page to interpret the token, so
759
+ * every new value costs a branch in TypeScript.
760
+ */
761
+ requires?: string;
762
+ /**
763
+ * Conditions on the page's published context, same grammar and same
764
+ * evaluator as a NaviGraph toolbar node, applied
765
+ * by {@link LayoutActionsService}:
766
+ *
767
+ * showWhen — the action does not APPLY, so it is absent.
768
+ * disabledWhen — temporarily UNAVAILABLE: stays put and greys out.
769
+ * busyWhen — the action is RUNNING: greys out too, and may relabel.
770
+ * busyLabel — label shown while `busyWhen` holds ("Saving…").
771
+ * activeWhen — pressed/active styling.
772
+ *
773
+ * Two carriers (a layout config and a NaviGraph tree) but ONE vocabulary:
774
+ * a page that moves between them should not have to relearn how to say
775
+ * "not now".
776
+ */
777
+ showWhen?: Record<string, unknown>;
778
+ disabledWhen?: Record<string, unknown>;
779
+ busyWhen?: Record<string, unknown>;
780
+ busyLabel?: string;
781
+ activeWhen?: Record<string, unknown>;
782
+ }
783
+ interface LayoutSection {
784
+ title?: string;
785
+ /**
786
+ * When true, the layout shell pins the section to the top of the
787
+ * scroll container (CSS `position: sticky`). Used by inspector /
788
+ * filter pages where an input form should stay visible while the
789
+ * operator scrolls through results below. Currently honoured by
790
+ * cms-inspector-layout; cms-detail-layout will follow when it
791
+ * lands. Default false.
792
+ */
793
+ sticky?: boolean;
794
+ slots?: Record<string, {
795
+ component?: string;
796
+ [key: string]: unknown;
797
+ }>;
798
+ [key: string]: unknown;
799
+ }
800
+ interface LayoutConfig {
801
+ id?: string;
802
+ template?: string;
803
+ title?: string;
804
+ subtitle?: string;
805
+ icon?: string;
806
+ slots?: Record<string, unknown>;
807
+ sections?: Record<string, LayoutSection>;
808
+ headerActions?: LayoutHeaderAction[];
809
+ /**
810
+ * Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)
811
+ * — primary / destructive actions kept out of the cramped header.
812
+ * Same shape as `headerActions`; gated per-item via `requires`.
813
+ */
814
+ footerActions?: LayoutHeaderAction[];
815
+ permissions?: string[];
816
+ [key: string]: unknown;
817
+ }
818
+ interface DialogConfig {
819
+ [key: string]: unknown;
820
+ }
821
+ interface NavigraphConfig {
822
+ [key: string]: unknown;
823
+ }
824
+ interface FormConfig {
825
+ [key: string]: unknown;
826
+ }
827
+ interface DatagridConfig {
828
+ [key: string]: unknown;
829
+ }
830
+ /**
831
+ * Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.
832
+ *
833
+ * Responses are cached for the lifetime of the service (Map + shareReplay(1))
834
+ * so repeated calls never re-fetch the same resource.
835
+ *
836
+ * configBase is read from the API manifest (manifest.configBase) and falls
837
+ * back to '/api/v1/config' when the manifest is not yet loaded.
838
+ */
839
+ declare class ConfigService {
840
+ private readonly http;
841
+ private readonly store;
842
+ private readonly cache;
843
+ layout(id: string): Observable<LayoutConfig>;
844
+ dialog(id: string): Observable<DialogConfig>;
845
+ navigraph(slug: string): Observable<NavigraphConfig>;
846
+ form(id: string): Observable<FormConfig>;
847
+ datagrid(id: string): Observable<DatagridConfig>;
848
+ private fetch;
849
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConfigService, never>;
850
+ static ɵprov: i0.ɵɵInjectableDeclaration<ConfigService>;
851
+ }
852
+
853
+ declare class SetAppConfig {
854
+ readonly config: ThemeConfigResponse;
855
+ static readonly type = "[AppConfig] Set";
856
+ constructor(config: ThemeConfigResponse);
857
+ }
858
+ interface AppConfigStateModel {
859
+ config: ThemeConfigResponse | null;
860
+ loaded: boolean;
861
+ }
862
+ declare class AppConfigState {
863
+ set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void;
864
+ static manifest(state: AppConfigStateModel): ApiManifest | null;
865
+ /**
866
+ * F.7 viewer manifest. Null when the backend didn't ship a viewers
867
+ * section (no tagged providers); consumers should fall back to a
868
+ * "no preview" surface.
869
+ */
870
+ static viewers(state: AppConfigStateModel): ViewerApiManifest | null;
871
+ static loaded(state: AppConfigStateModel): boolean;
872
+ static ɵfac: i0.ɵɵFactoryDeclaration<AppConfigState, never>;
873
+ static ɵprov: i0.ɵɵInjectableDeclaration<AppConfigState>;
874
+ }
875
+
876
+ /**
877
+ * Converts any caught error into a human-readable message.
878
+ *
879
+ * Priority order for backend errors:
880
+ * 1. error.detail (RFC 7807 problem detail)
881
+ * 2. error['hydra:description'] (Hydra validation errors)
882
+ * 3. error.message (generic)
883
+ * 4. error.violations[] (constraint violations)
884
+ * 5. Status-based fallback
885
+ */
886
+ declare class ErrorHandlerService {
887
+ humanize(err: unknown): string;
888
+ private statusMessage;
889
+ static ɵfac: i0.ɵɵFactoryDeclaration<ErrorHandlerService, never>;
890
+ static ɵprov: i0.ɵɵInjectableDeclaration<ErrorHandlerService>;
891
+ }
892
+
893
+ /** What the user PICKED. `system` defers to the OS. */
894
+ type ThemeChoice = 'light' | 'dark' | 'system';
895
+ /** What that resolves to right now — the only two the stylesheet knows. */
896
+ type ResolvedTheme = 'light' | 'dark';
897
+ /**
898
+ * Applies the user's theme by setting `data-theme` on the document element.
899
+ *
900
+ * The stylesheet does the rest: `:root[data-theme='dark']` re-points the
901
+ * `--cms-*` palette and every colour in the admin arrives through those names.
902
+ * Nothing here knows a single colour, which is the point — a second
903
+ * theme is a block of tokens, not a code change.
904
+ *
905
+ * Follows the shape of UserCalendarPreferencesService and
906
+ * CallOverlayPreferencesService: lazy one-shot load of `/auth/me/settings`,
907
+ * cached for the SPA lifetime, with `update()` called by the Profile page after
908
+ * a save so the change lands without a refetch.
909
+ */
910
+ declare class ThemeService {
911
+ private readonly api;
912
+ private readonly store;
913
+ private readonly _choice;
914
+ private readonly _systemDark;
915
+ private readonly _userAccent;
916
+ private readonly _platformAccent;
917
+ private loadOnce$?;
918
+ /** The user's stored choice, including `system`. */
919
+ readonly choice: i0.Signal<ThemeChoice>;
920
+ /** This user's personal override, independent of the deployment's. */
921
+ readonly userAccent: i0.Signal<string | null>;
922
+ /** The deployment's brand accent from the API manifest, if it set one. */
923
+ readonly platformAccent: i0.Signal<string | null>;
924
+ /**
925
+ * The colour actually painted: personal override, else the deployment's,
926
+ * else null — which leaves the stylesheet's own accent in place.
927
+ *
928
+ * Three rungs and each is a real state. A user who has chosen nothing is
929
+ * NOT the same as one who chose the deployment's colour: clear the
930
+ * deployment's brand and the first user follows it, the second does not.
931
+ */
932
+ readonly accent: i0.Signal<string | null>;
933
+ /** The choice with `system` resolved against the OS setting. */
934
+ readonly resolved: i0.Signal<ResolvedTheme>;
935
+ constructor();
936
+ /**
937
+ * One-shot load of the stored preference. Safe to call more than once and
938
+ * safe for anonymous users — a failure leaves the cached/system value in
939
+ * place rather than forcing light, so the login screen still matches the OS.
940
+ */
941
+ ensureLoaded(): Observable<ThemeChoice>;
942
+ /**
943
+ * Called by the Profile page after the Preferences tab saves, so the admin
944
+ * re-themes on the spot. Ignores anything that is not a known choice —
945
+ * the section PATCH returns the whole merged bag, not just this field.
946
+ */
947
+ update(value: unknown): void;
948
+ /**
949
+ * Companion to {@link update} for the accent field of the same save.
950
+ * An explicit null clears the override; anything malformed is ignored
951
+ * rather than applied, so a bad value can never blank the admin's accent.
952
+ */
953
+ updateAccent(value: unknown): void;
954
+ private set;
955
+ /**
956
+ * The manifest is loaded at bootstrap, well before the authenticated shell
957
+ * calls this, so a snapshot read is enough and no subscription is needed.
958
+ * A malformed value is ignored rather than applied — the container refuses
959
+ * to build with one, but the manifest crosses the network all the same.
960
+ */
961
+ private readPlatformAccent;
962
+ private setAccent;
963
+ /**
964
+ * Writes the override onto the document element, where an inline style
965
+ * outranks every stylesheet rule — including the dark block — so one
966
+ * declaration re-points the accent in both themes.
967
+ *
968
+ * Only --cms-accent is set, not the -hover/-light/-text members of the
969
+ * family. Deriving those from an arbitrary user colour needs a colour model
970
+ * the palette does not have yet, and guessing them would produce hover
971
+ * states that clash with the very colour the user chose. Until then the
972
+ * override moves the brand fill and leaves its supporting tones alone.
973
+ */
974
+ private applyAccent;
975
+ private applyToDocument;
976
+ private static readCache;
977
+ private static readAccentCache;
978
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
979
+ static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
980
+ }
981
+
982
+ /**
983
+ * The platform's loading indicator: the exclamation mark from Cool!MS,
984
+ * drawing itself.
985
+ *
986
+ * ## Why a mark and not a spinner
987
+ *
988
+ * A spinner says "something is happening" and nothing else; every product has
989
+ * one and none of them are yours. The bang is already the one glyph in the
990
+ * wordmark that carries the brand, so a loader built from it says whose
991
+ * software is thinking — for free, in the place a user is already looking.
992
+ *
993
+ * ## Why one component rather than a snippet per surface
994
+ *
995
+ * It is meant for every wait long enough to notice: a dashboard assembling its
996
+ * cards, an editor paginating a document, an explorer listing a folder. Those
997
+ * are different teams' files, and a copied SVG in each is how three loaders
998
+ * that no longer match each other happen.
999
+ *
1000
+ * ## The animation
1001
+ *
1002
+ * The stem wipes upward and the dot lands after it, on a loop — the shape being
1003
+ * WRITTEN rather than spun. It is deliberately calm: a loader is background
1004
+ * furniture, and anything with a hard beat gets irritating on the third viewing.
1005
+ * Under `prefers-reduced-motion` it holds still and only breathes, because
1006
+ * motion sickness is not a stylistic preference.
1007
+ */
1008
+ declare class CmsLoaderComponent {
1009
+ /** Optional caption. Absent renders the mark alone, which suits a small inline wait. */
1010
+ readonly label: i0.InputSignal<string>;
1011
+ /** Cover the positioned ancestor rather than sitting in the flow. */
1012
+ readonly overlay: i0.InputSignal<boolean>;
1013
+ /**
1014
+ * Row layout at text scale, for a wait inside a row, a status bar or a
1015
+ * button. Override `--cms-loader-size` to tune the mark.
1016
+ */
1017
+ readonly inline: i0.InputSignal<boolean>;
1018
+ /**
1019
+ * A clip-path needs a document-unique id, and this component is used more
1020
+ * than once per page — two loaders sharing an id makes the second one clip
1021
+ * against the first one's rectangle and stop animating.
1022
+ */
1023
+ protected readonly clipId: string;
1024
+ static ɵfac: i0.ɵɵFactoryDeclaration<CmsLoaderComponent, never>;
1025
+ static ɵcmp: i0.ɵɵComponentDeclaration<CmsLoaderComponent, "cms-loader", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "overlay": { "alias": "overlay"; "required": false; "isSignal": true; }; "inline": { "alias": "inline"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1026
+ }
1027
+
1028
+ /**
1029
+ * Unified user preferences service — all preference types share one
1030
+ * localStorage key (`coolms_ui_prefs`) and one debounced server-sync pipeline.
1031
+ *
1032
+ * Key naming convention (stored as nested objects, referenced via dot-notation):
1033
+ *
1034
+ * // Grids
1035
+ * grids.{gridId}.columns
1036
+ * grids.{gridId}.columnWidths
1037
+ *
1038
+ * // Panels (ExplorerLayout persistKey values from YAML)
1039
+ * panel.{persistKey}.width
1040
+ * panel.{persistKey}.collapsed
1041
+ *
1042
+ * // Page navigation state
1043
+ * page.vfs.lastPath
1044
+ * page.vfs.viewMode
1045
+ * page.media.lastDir
1046
+ * page.media.viewMode
1047
+ *
1048
+ * // Global
1049
+ * nav.lastRoute
1050
+ * terminal.height
1051
+ * terminal.open
1052
+ */
1053
+ declare class UserPreferencesService {
1054
+ private readonly http;
1055
+ private readonly store;
1056
+ private readonly destroyRef;
1057
+ private readonly STORAGE_KEY;
1058
+ private readonly SYNC_DELAY;
1059
+ private syncSubject;
1060
+ constructor();
1061
+ /**
1062
+ * Write all prefs to localStorage and schedule a debounced server sync.
1063
+ */
1064
+ private save;
1065
+ /**
1066
+ * Read the entire prefs object from localStorage, applying legacy key
1067
+ * migration on the first read after an upgrade.
1068
+ */
1069
+ private load;
1070
+ /**
1071
+ * One-time migration of legacy flat keys to the new nested naming convention.
1072
+ * Mutates `all` in place and re-persists only when something changed.
1073
+ */
1074
+ private migrate;
1075
+ /**
1076
+ * Seed localStorage from server preferences.
1077
+ * Called after login and on every session restore (page reload).
1078
+ *
1079
+ * Strategy:
1080
+ * - No local prefs yet (new device / clean install) -> use server prefs as-is.
1081
+ * - Local prefs already exist -> merge with local taking priority at each
1082
+ * top-level namespace. This ensures that locally-persisted state
1083
+ * (panel widths, last path, etc.) is never overwritten by the stale copy
1084
+ * that was saved in the auth token at login time.
1085
+ */
1086
+ loadFromServer(uiPrefs: Record<string, unknown>): void;
1087
+ private syncToServer;
1088
+ /**
1089
+ * Get stored preferences for a specific grid.
1090
+ */
1091
+ getGridPref(gridId: string): DataGridPreference | null;
1092
+ /**
1093
+ * Set visible column order for a grid.
1094
+ * Applies to localStorage instantly; server sync is debounced.
1095
+ */
1096
+ setColumns(gridId: string, columns: string[]): void;
1097
+ /**
1098
+ * Record a column width for a grid column.
1099
+ * Applies to localStorage instantly; server sync is debounced.
1100
+ */
1101
+ setColumnWidth(gridId: string, field: string, width: number): void;
1102
+ /**
1103
+ * Remove all preferences for a specific grid and immediately sync to server.
1104
+ */
1105
+ resetGrid(gridId: string): void;
1106
+ private updateGrid;
1107
+ /**
1108
+ * Get persisted terminal panel height.
1109
+ */
1110
+ getTerminalHeight(): number | null;
1111
+ /**
1112
+ * Persist terminal panel height.
1113
+ */
1114
+ setTerminalHeight(height: number): void;
1115
+ /**
1116
+ * Get persisted state for a resizable panel identified by key.
1117
+ * Used by ExplorerLayoutComponent to persist panel widths across sessions.
1118
+ */
1119
+ getPanelState(key: string): {
1120
+ width: number;
1121
+ collapsed: boolean;
1122
+ } | null;
1123
+ /**
1124
+ * Persist generic panel state by key.
1125
+ * Applies to localStorage instantly; server sync is debounced.
1126
+ */
1127
+ setPanelState(key: string, width: number, collapsed: boolean): void;
1128
+ /**
1129
+ * Get all persisted state for a page namespace (e.g. 'vfs', 'media', 'nav').
1130
+ */
1131
+ getPageState<T>(pageKey: string): T | null;
1132
+ /**
1133
+ * Merge partial state into a page namespace.
1134
+ * Existing keys in the namespace are preserved; only provided keys are updated.
1135
+ * Applies to localStorage instantly; server sync is debounced.
1136
+ */
1137
+ setPageState(pageKey: string, state: Record<string, unknown>): void;
1138
+ /** @deprecated Use getPageState('media') instead */
1139
+ getMediaViewMode(): string | null;
1140
+ /** @deprecated Use setPageState('media', ...) instead */
1141
+ setMediaViewMode(mode: string): void;
1142
+ /** @deprecated Use getPanelState('media_panel_left') instead */
1143
+ getMediaCollectionsPanel(): {
1144
+ width: number;
1145
+ collapsed: boolean;
1146
+ } | null;
1147
+ /** @deprecated Use setPanelState('media_panel_left', ...) instead */
1148
+ setMediaCollectionsPanel(width: number, collapsed: boolean): void;
1149
+ static ɵfac: i0.ɵɵFactoryDeclaration<UserPreferencesService, never>;
1150
+ static ɵprov: i0.ɵɵInjectableDeclaration<UserPreferencesService>;
1151
+ }
1152
+
1153
+ /**
1154
+ * Sidebar collapse state -- two independent dimensions:
1155
+ *
1156
+ * - `collapsed`: whole-sidebar icon-only rail toggle (the chevron at
1157
+ * the bottom of the sidebar). Per-device localStorage.
1158
+ * - `collapsedSections`: per-section collapse, keyed by the section
1159
+ * separator's node id. Used to fold a section's items under their
1160
+ * header so the nav doesn't endless-scroll once a tenant has lots
1161
+ * of modules. Per-device localStorage.
1162
+ * - `collapsedItems`: per-item inline-expand collapse, keyed by the
1163
+ * nav node's id. Mirrors `collapsedSections` but for sub-tree items
1164
+ * (a top-level item with children, e.g. Reports -> Process Report).
1165
+ * Absent id = expanded (children visible); present = collapsed.
1166
+ * Per-device localStorage.
1167
+ *
1168
+ * Storage failures (private mode, quota, disabled cookies) fall back
1169
+ * to defaulting un-collapsed -- the toggles still work for the session,
1170
+ * just don't persist.
1171
+ */
1172
+ declare class SidebarStateService {
1173
+ private static readonly STORAGE_KEY;
1174
+ private static readonly STORAGE_KEY_SECTIONS;
1175
+ private static readonly STORAGE_KEY_ITEMS;
1176
+ readonly collapsed: i0.WritableSignal<boolean>;
1177
+ readonly collapsedSections: i0.WritableSignal<Set<string>>;
1178
+ readonly collapsedItems: i0.WritableSignal<Set<string>>;
1179
+ toggle(): void;
1180
+ isSectionCollapsed(id: string): boolean;
1181
+ toggleSection(id: string): void;
1182
+ /**
1183
+ * Force a section to be expanded. Used when navigation lands on
1184
+ * a route inside a manually-collapsed section -- it would be
1185
+ * confusing to leave the active item hidden. The user can
1186
+ * collapse the section again afterwards.
1187
+ */
1188
+ ensureExpanded(id: string): void;
1189
+ isItemCollapsed(id: string): boolean;
1190
+ toggleItem(id: string): void;
1191
+ private readPersisted;
1192
+ private persist;
1193
+ private readSectionsPersisted;
1194
+ private persistSections;
1195
+ private readItemsPersisted;
1196
+ private persistItems;
1197
+ static ɵfac: i0.ɵɵFactoryDeclaration<SidebarStateService, never>;
1198
+ static ɵprov: i0.ɵɵInjectableDeclaration<SidebarStateService>;
1199
+ }
1200
+
1201
+ /**
1202
+ * One row returned by the `/api/entity-search` endpoint. `id` is
1203
+ * what the picker emits via its `valueChange` output (and what the
1204
+ * template's persisted context carries). `label` is the primary
1205
+ * display line. `secondary` is an optional second line for
1206
+ * disambiguation (email, slug, etc.) — the backend omits the key
1207
+ * when there is nothing to show; receiving `null` from a custom
1208
+ * resolver is normalized to `undefined` here.
1209
+ */
1210
+ interface EntitySearchResult {
1211
+ readonly id: string | number;
1212
+ readonly label: string;
1213
+ readonly secondary?: string;
1214
+ }
1215
+ /**
1216
+ * HTTP client for the Phase 2a `/api/entity-search` endpoint.
1217
+ * Returns a stream of search results for the given entity FQCN.
1218
+ * Permission filtering happens at the backend resolver layer —
1219
+ * this service is a thin RPC.
1220
+ */
1221
+ declare class EntitySearchService {
1222
+ private readonly http;
1223
+ /**
1224
+ * @param entityType Fully-qualified PHP class name of the entity
1225
+ * being searched (e.g. the backend's User entity).
1226
+ * @param query Free-text search input. Empty string is
1227
+ * valid — the backend returns the default
1228
+ * listing for that entity type.
1229
+ * @param limit Max rows to return; clamped server-side to
1230
+ * [1, 100].
1231
+ */
1232
+ search(entityType: string, query: string, limit?: number): Observable<EntitySearchResult[]>;
1233
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntitySearchService, never>;
1234
+ static ɵprov: i0.ɵɵInjectableDeclaration<EntitySearchService>;
1235
+ }
1236
+
1237
+ /**
1238
+ * Well-known click target values embedded in meta.target.
1239
+ * 'action.logout' — dispatch Logout action
1240
+ * 'route' — navigate via routerLink (default when target absent)
1241
+ * '_blank' — open in a new tab (hrefOverride must be set)
1242
+ */
1243
+ type NaviGraphTarget = 'action.logout' | 'route' | '_blank' | (string & {});
1244
+ interface NaviGraphMeta {
1245
+ readonly component?: string;
1246
+ readonly icon?: string;
1247
+ readonly routerLink?: string;
1248
+ readonly target?: NaviGraphTarget;
1249
+ readonly requiredRole?: string;
1250
+ readonly voterAttribute?: string;
1251
+ readonly contributor?: string;
1252
+ readonly [key: string]: unknown;
1253
+ }
1254
+ /** Flat node as returned from GET /navi/trees/{slug}/graph */
1255
+ interface NaviGraphNodeFlat {
1256
+ readonly id: string;
1257
+ readonly path: string;
1258
+ readonly title: string;
1259
+ readonly parentId: string | null;
1260
+ readonly sortOrder: number;
1261
+ readonly isActive: boolean;
1262
+ readonly isVisible: boolean;
1263
+ readonly meta: NaviGraphMeta;
1264
+ }
1265
+ /** Same node enriched with resolved children after client-side tree build */
1266
+ interface NaviGraphNode extends NaviGraphNodeFlat {
1267
+ readonly children: NaviGraphNode[];
1268
+ }
1269
+
1270
+ /**
1271
+ * Loads, caches, and drives admin NaviGraph trees.
1272
+ *
1273
+ * Usage:
1274
+ * naviGraphService.loadAdminNav(url).subscribe();
1275
+ * const items = naviGraphService.adminNav(); // signal<NaviGraphNode[]>
1276
+ * naviGraphService.handleClick(node);
1277
+ */
1278
+ declare class NaviGraphService {
1279
+ private readonly http;
1280
+ private readonly router;
1281
+ private readonly store;
1282
+ private readonly errors;
1283
+ /** Resolved root nodes for navi.admin (sidebar). */
1284
+ readonly adminNav: i0.WritableSignal<NaviGraphNode[]>;
1285
+ /** Resolved root nodes for navi.admin.topbar. */
1286
+ readonly topbarNav: i0.WritableSignal<NaviGraphNode[]>;
1287
+ private readonly cache;
1288
+ loadAdminNav(url: string): Observable<NaviGraphNode[]>;
1289
+ loadTopbarNav(url: string): Observable<NaviGraphNode[]>;
1290
+ /**
1291
+ * Handle a nav-node click.
1292
+ *
1293
+ * Routing logic based on meta.target:
1294
+ * 'action.logout' — dispatch Logout
1295
+ * '_blank' — window.open (uses node.path as href)
1296
+ * default — router.navigate with meta.routerLink or node.path
1297
+ */
1298
+ handleClick(node: NaviGraphNode): void;
1299
+ /**
1300
+ * Evaluate `showWhen` meta condition against a record.
1301
+ * Nodes with no `showWhen` are always visible.
1302
+ * Supports nested AND/OR logic: { and: [...] } / { or: [...] }
1303
+ */
1304
+ isVisible(node: NaviGraphNode, record: Record<string, unknown>): boolean;
1305
+ /**
1306
+ * Standalone `showWhen` evaluator for callers that don't have a
1307
+ * full `NaviGraphNode` (e.g. a grid's row context menu — a
1308
+ * `rowAction.showWhen` predicate is evaluated against the row's
1309
+ * own projection). `null`/`undefined` predicates default to
1310
+ * visible, mirroring `isVisible`'s "no rule = always shown"
1311
+ * semantics.
1312
+ */
1313
+ matchesShowWhen(showWhen: Record<string, unknown> | undefined | null, record: Record<string, unknown>): boolean;
1314
+ private evalCondition;
1315
+ /**
1316
+ * Fetch a graph endpoint and return a nested tree.
1317
+ * Handles both compact JSON-LD IRIs (`member`) and
1318
+ * the legacy Hydra key (`hydra:member`).
1319
+ * Results are cached by URL for the lifetime of the service instance.
1320
+ */
1321
+ loadTree(url: string): Observable<NaviGraphNode[]>;
1322
+ /**
1323
+ * Build a nested tree from a flat array of nodes.
1324
+ * Nodes are sorted by sortOrder at each level.
1325
+ */
1326
+ private buildTree;
1327
+ static ɵfac: i0.ɵɵFactoryDeclaration<NaviGraphService, never>;
1328
+ static ɵprov: i0.ɵɵInjectableDeclaration<NaviGraphService>;
1329
+ }
1330
+
1331
+ /**
1332
+ * Registry mapping string keys to Angular component classes.
1333
+ *
1334
+ * Backed by a static Map so components registered at module load time
1335
+ * (e.g. in app.config.ts) are visible to all injected instances.
1336
+ *
1337
+ * Usage — static (app.config.ts / bootstrap time):
1338
+ * ComponentRegistry.register('MediaLibraryPage', MediaLibraryPage);
1339
+ *
1340
+ * Usage — injected (SlotComponent / runtime):
1341
+ * private readonly registry = inject(ComponentRegistry);
1342
+ * const cls = this.registry.get('MediaLibraryPage');
1343
+ */
1344
+ declare class ComponentRegistry {
1345
+ private static readonly map;
1346
+ static register(key: string, component: Type<unknown>): void;
1347
+ static get(key: string): Type<unknown> | undefined;
1348
+ static has(key: string): boolean;
1349
+ static keys(): string[];
1350
+ register(key: string, component: Type<unknown>): void;
1351
+ get(key: string): Type<unknown> | null;
1352
+ has(key: string): boolean;
1353
+ keys(): string[];
1354
+ static ɵfac: i0.ɵɵFactoryDeclaration<ComponentRegistry, never>;
1355
+ static ɵprov: i0.ɵɵInjectableDeclaration<ComponentRegistry>;
1356
+ }
1357
+
1358
+ /**
1359
+ * Phase H7 -- admin Site Selector.
1360
+ *
1361
+ * Stamps `X-CoolMS-Section: <slug>` on outgoing `/api/v1/*` requests when
1362
+ * the admin has picked a section from the Site Selector dropdown. The backend
1363
+ * honours it for AUTHENTICATED requests only -- anonymous traffic cannot pivot
1364
+ * the active section.
1365
+ *
1366
+ * What the header actually reaches, counted rather than intended: exactly two
1367
+ * readers act on it, `CreatePageProcessor` and `CreateCollectionProcessor`,
1368
+ * each choosing WHICH SITE a newly created page or collection lands in -- and,
1369
+ * for a page, that section's default locale and slug-naming policy. Nothing
1370
+ * else is scoped by it. Media spaces list every active section regardless, and
1371
+ * every other reader of `_coolms_section` sits on the public SSR surface,
1372
+ * which resolves from host+path and never sees this header.
1373
+ *
1374
+ * This block used to say the backend "scopes site-specific API providers
1375
+ * (pages, media spaces, etc.)". That described where was heading --
1376
+ * H4/H5, multisite Document and Media, never landed -- not what it does. Left
1377
+ * recorded because a docstring naming an intended destination reads exactly
1378
+ * like one naming a shipped feature, and this one misdescribed the control for
1379
+ * three months.
1380
+ *
1381
+ * Skipped:
1382
+ * - non-API URLs (no section context needed for SSR/static)
1383
+ * - `/auth/*` endpoints (login/refresh are pre-session; the header would
1384
+ * be ignored by the backend anyway, but skipping keeps wire traffic clean)
1385
+ * - requests that already carry the header (programmatic overrides win)
1386
+ */
1387
+ declare const sectionInterceptor: HttpInterceptorFn;
1388
+
1389
+ /**
1390
+ * The one fact the section interceptor needs: which site the operator is
1391
+ * currently working in.
1392
+ *
1393
+ * `core` is auth, config, errors and interceptors -- the layer everything else
1394
+ * is built on -- so it must not import a feature. The obvious-looking fix,
1395
+ * moving `SectionState` into core, is the wrong one: sections are a CMS domain
1396
+ * concept, and hoisting domain state into the base layer trades one bad
1397
+ * direction for another. So core declares the little it needs and the module
1398
+ * that owns the state provides it, bound in the composition root.
1399
+ *
1400
+ * Read SYNCHRONOUSLY on purpose. An interceptor decides whether to stamp a
1401
+ * header while building the request; it cannot wait on a stream.
1402
+ */
1403
+ interface CurrentSectionPort {
1404
+ /** Slug of the active section, or null when the operator picked none. */
1405
+ currentSlug(): string | null;
1406
+ }
1407
+ /**
1408
+ * Optional by design: an application assembled without a Sections module binds
1409
+ * nothing and simply sends no `X-CoolMS-Section` header, which is exactly what
1410
+ * a single-site install wants.
1411
+ */
1412
+ declare const CURRENT_SECTION: InjectionToken<CurrentSectionPort>;
1413
+
1414
+ export { AppConfigState, AppInitService, AuthRefreshCoordinator, AuthState, BYPASS_AUTH, CURRENT_SECTION, CmsLoaderComponent, ComponentRegistry, ConfigService, CrossTabAuthSyncService, EntitySearchService, ErrorHandlerService, IdentityApiClient, Login, LoginComponent, Logout, NaviGraphService, PatchCurrentUser, RealtimeTokenClient, RestoreSession, SetAppConfig, SetTokens, SidebarStateService, ThemeService, UserPreferencesService, authGuard, authInterceptor, loginPageGuard, resolvePattern, sectionInterceptor };
1415
+ export type { ApiManifest, AppConfigStateModel, AuthApiManifest, AuthStateModel, CentrifugoConnectionTokenDto, CentrifugoSubscriptionTokenDto, ContentApiManifest, CurrentSectionPort, DataGridApiManifest, DataGridPreference, DataGridPreferences, DataSourceDefinition, DataSourceLoading, DataSourceOption, DataSourceType, DataSourceWidget, DatagridConfig, DialogConfig, DocumentApiManifest, DomainExplorerApiManifest, DynamicEntityApiManifest, EditorApiManifest, EditorApiManifestEntry, EditorApiManifestProfile, EntitySearchResult, FieldItem, FieldSecurityPolicy, FieldType, FormAction, FormActionType, FormActionVariant, FormConfig, FormRenderDefinition, HydraCollection, HydraView, IdentityApiManifest, LayoutConfig, LayoutHeaderAction, LayoutNode, LayoutSection, MediaApiManifest, NaviApiManifest, NaviGraphMeta, NaviGraphNode, NaviGraphNodeFlat, NaviGraphTarget, NavigraphConfig, PlatformDefaults, RelationDefinition, ResolvedTheme, SectionApiManifest, SubFormDefinition, TerminalApiManifest, ThemeChoice, ThemeConfigResponse, TokenResponse, UserDto, ValidatorDefinition, VfsApiManifest, ViewerApiManifest, ViewerDefinitionApiManifest, ViewerProfileApiManifest, VisibilityCondition, VisibilityOperator };