@coolms/core-angular 2.0.0-alpha.3 → 2.0.0-alpha.5

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.
@@ -1,9 +1,9 @@
1
1
  import * as rxjs from 'rxjs';
2
- import { Observable } from 'rxjs';
2
+ import { Observable, Subject } from 'rxjs';
3
3
  import * as i0 from '@angular/core';
4
- import { Type, InjectionToken } from '@angular/core';
4
+ import { InjectionToken, Type, Provider, EnvironmentProviders } from '@angular/core';
5
5
  import { StateContext } from '@ngxs/store';
6
- import { CanActivateFn } from '@angular/router';
6
+ import { CanActivateFn, Routes, CanMatchFn } from '@angular/router';
7
7
  import { HttpContextToken, HttpInterceptorFn } from '@angular/common/http';
8
8
 
9
9
  interface AuthApiManifest {
@@ -79,6 +79,44 @@ interface VfsApiManifest {
79
79
  */
80
80
  interface DocumentApiManifest {
81
81
  readonly spacesUrl?: string;
82
+ /**
83
+ * !! Carried here rather than derived from `spacesUrl`. Concatenating
84
+ * `/available` onto that would couple the client to a URL shape the router
85
+ * owns, and it would keep working until the route moved -- then fail as a
86
+ * 404 the UI reports as "no sites available", which is indistinguishable
87
+ * from the true empty answer.
88
+ *
89
+ * !! ABSENT ARRIVES AS `''`, NOT `undefined`. The producer declares these
90
+ * as non-nullable strings defaulting to empty, so an installation that has
91
+ * not set them sends an empty string and the `?` here never fires.
92
+ *
93
+ * Guard on FALSINESS -- `!url`, `Boolean(url)`. The dangerous rewrite is
94
+ * `url === undefined` (or `!== undefined`), which reads as more precise and
95
+ * treats `''` as present, handing a caller a request to the empty string.
96
+ *
97
+ * !! A `?? fallback` is NOT the hazard, though it looks like the obvious
98
+ * one: `'' ?? x` is `''`, so a fallback never fires on the value this
99
+ * producer actually sends. Measured by mutation -- adding one changed
100
+ * nothing, changing the guard broke three assertions.
101
+ */
102
+ readonly spacesAvailableUrl?: string;
103
+ readonly spaceEnablementUrl?: string;
104
+ /**
105
+ * FQCN of the entity a filter-mode audience can be built from.
106
+ *
107
+ * !! The SERVER owns this and the client must not know it. The generation
108
+ * wizard offers Filter mode only when a template's context schema
109
+ * references this type, and it used to compare against a constant compiled
110
+ * into this bundle -- which shipped the consuming application's class name
111
+ * inside a published package, and tied that package to an installation
112
+ * having that exact class.
113
+ *
114
+ * !! Absent arrives as `''`, like the urls above. Treat empty as **filter
115
+ * mode unavailable**, never as a reason to fall back to a remembered
116
+ * value: the server is the thing that rejects every other type, so a guess
117
+ * here offers a mode the server will refuse.
118
+ */
119
+ readonly filterAudienceEntity?: string;
82
120
  }
83
121
  interface DynamicEntityApiManifest {
84
122
  readonly typesUrl: string;
@@ -116,6 +154,13 @@ interface IdentityApiManifest {
116
154
  readonly settingsSectionUrl: string;
117
155
  readonly colorUrl: string;
118
156
  readonly rolesUrl?: string;
157
+ readonly elevationUrl?: string;
158
+ readonly deletionsUrl?: string;
159
+ readonly footprintsUrl?: string;
160
+ readonly userDeletionUrl?: string;
161
+ readonly userLegalHoldsUrl?: string;
162
+ readonly holdsSettingsBlock?: string;
163
+ readonly legalHoldFormId?: string | null;
119
164
  }
120
165
  /**
121
166
  * Editor sub-manifest emitted by the backend's Editor module. The
@@ -153,7 +198,7 @@ interface EditorApiManifest {
153
198
  * Viewer manifest emitted by the backend's Document module.
154
199
  * Each viewer declares the MIME types / extensions it handles, the
155
200
  * Angular component selector to dispatch into, and one config blob per
156
- * profile. Profile config is open-ended — every viewer interprets the
201
+ * profile. Profile config is open-ended -- every viewer interprets the
157
202
  * keys it understands and ignores the rest.
158
203
  */
159
204
  interface ViewerProfileApiManifest {
@@ -190,8 +235,29 @@ interface PlatformDefaults {
190
235
  */
191
236
  readonly accentColor?: string | null;
192
237
  }
238
+ /**
239
+ * The host contracts in force here (the platform rule: hosts implement
240
+ * contracts, modules offer entries): for each contract, what the one
241
+ * installed theme implementing it declares, and the installed modules whose
242
+ * entries the installer matched against it. A host mounts a module only when
243
+ * it is listed.
244
+ */
245
+ interface UiApiManifest {
246
+ /** The contracts in force, name -> MAJOR.MINOR (`{ console: '1.0' }`): one installed theme implements each. */
247
+ readonly contracts: Readonly<Record<string, string>>;
248
+ /** Which installed theme implements each contract, name -> theme slug. */
249
+ readonly hosts?: Readonly<Record<string, string>>;
250
+ readonly modules: ReadonlyArray<UiModuleEntryManifest>;
251
+ }
252
+ interface UiModuleEntryManifest {
253
+ readonly module: string;
254
+ readonly contract: string;
255
+ readonly range: string;
256
+ readonly framework: string;
257
+ }
193
258
  interface ApiManifest {
194
259
  readonly apiBase: string;
260
+ readonly ui?: UiApiManifest;
195
261
  readonly configBase?: string;
196
262
  readonly auth?: AuthApiManifest;
197
263
  readonly identity?: IdentityApiManifest;
@@ -213,6 +279,15 @@ interface ApiManifest {
213
279
  }>;
214
280
  readonly platformDefaults?: PlatformDefaults;
215
281
  }
282
+ /**
283
+ * GET /api/v1/admin/manifest -- the admin's endpoint map, answered to every signed-in account
284
+ * since 2026-10-06 (no host groups: each call it names is decided by its own route). A server
285
+ * from before that answers at /api/v1/console/manifest, to the console group only. Read once
286
+ * signed in.
287
+ */
288
+ interface ConsoleManifestResponse {
289
+ readonly manifest: ApiManifest;
290
+ }
216
291
  interface ThemeConfigResponse {
217
292
  readonly slug?: string | null;
218
293
  readonly feStack?: string | null;
@@ -357,7 +432,7 @@ interface LayoutNode {
357
432
  }
358
433
  type FormActionType = 'submit' | 'button' | 'reset';
359
434
  type FormActionVariant = 'primary' | 'secondary' | 'danger' | 'link';
360
- /** Terminal form button (Submit / Cancel / …) — mirrors PHP FormAction.
435
+ /** Terminal form button (Submit / Cancel / ...) -- mirrors PHP FormAction.
361
436
  * Resolved by the backend (declared `formOptions.actions`, else lifted from
362
437
  * legacy submit fields, else a default Submit). `label` is already translated. */
363
438
  interface FormAction {
@@ -434,7 +509,7 @@ declare class IdentityApiClient {
434
509
  me(): Observable<UserDto>;
435
510
  getSettings(): Observable<Record<string, Record<string, unknown>>>;
436
511
  static ɵfac: i0.ɵɵFactoryDeclaration<IdentityApiClient, never>;
437
- static ɵprov: i0.ɵɵInjectableDeclaration<IdentityApiClient>;
512
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
438
513
  }
439
514
 
440
515
  /**
@@ -446,6 +521,13 @@ interface CentrifugoConnectionTokenDto {
446
521
  expiresAt: number;
447
522
  ttl: number;
448
523
  wsUrl: string;
524
+ /**
525
+ * The code the server closes this connection with when the caller's session
526
+ * ended (a sign-out everywhere, a password change, a deactivation). On it the
527
+ * client signs out and ends its calls at once, without reconnecting or waiting
528
+ * for a 401 (2026-09-26). Absent from a server older than that.
529
+ */
530
+ sessionEndedCode?: number;
449
531
  }
450
532
  /**
451
533
  * Response of `POST /centrifugo/subscription-token`: a per-channel JWT. The
@@ -471,15 +553,33 @@ interface CentrifugoSubscriptionTokenDto {
471
553
  *
472
554
  * `ApiService` keeps its identical signatures and delegates here, so nothing
473
555
  * calling it had to change.
556
+ *
557
+ * !! Access first, then realtime (Dmitry, 2026-09-28): "The console opens a realtime
558
+ * connection before the access check. Reverse the order ... A refused account opens no
559
+ * socket." The socket cannot open without the connection token, and anything may ask for
560
+ * one early -- the ring subscription is built at app init and asks as soon as a user is
561
+ * set, before the guard's answer is in. So the token waits for the console's answer (the
562
+ * guard's own request, not a second one) and a refused sign-in gets none: no request, no
563
+ * socket. A failure to ask is not a refusal, as at the guard.
564
+ *
565
+ * Since 2026-10-06 the connection token is every signed-in account's (Dmitry: "The connection
566
+ * token only opens a socket; each channel is authorized when it is subscribed to"), and a
567
+ * current server answers the admin manifest to every signed-in account, so no
568
+ * sign-in is refused and every one gets its token; each subscription is then decided by its
569
+ * channel. The wait stays for a server from before that, where a refused sign-in still opens
570
+ * no socket.
474
571
  */
475
572
  declare class RealtimeTokenClient {
573
+ /** The error a refused sign-in's connection-token request ends with. */
574
+ static readonly REFUSED = "no console access: no realtime connection";
476
575
  private readonly http;
477
576
  private readonly store;
577
+ private readonly injector;
478
578
  private get manifest();
479
579
  connectionToken(): Observable<CentrifugoConnectionTokenDto>;
480
580
  subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto>;
481
581
  static ɵfac: i0.ɵɵFactoryDeclaration<RealtimeTokenClient, never>;
482
- static ɵprov: i0.ɵɵInjectableDeclaration<RealtimeTokenClient>;
582
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
483
583
  }
484
584
 
485
585
  declare class Login {
@@ -533,7 +633,7 @@ declare class AuthState {
533
633
  static expiresAt(state: AuthStateModel): string | null;
534
634
  static currentUser(state: AuthStateModel): UserDto | null;
535
635
  static ɵfac: i0.ɵɵFactoryDeclaration<AuthState, never>;
536
- static ɵprov: i0.ɵɵInjectableDeclaration<AuthState>;
636
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
537
637
  }
538
638
 
539
639
  /**
@@ -541,10 +641,16 @@ declare class AuthState {
541
641
  *
542
642
  * The guard is intentionally ASYNC: it waits for AppInitService.ready$ before
543
643
  * evaluating auth state. Without this, Angular's router can evaluate
544
- * isAuthenticated while APP_INITIALIZER is still in-flight — RestoreSession
644
+ * isAuthenticated while APP_INITIALIZER is still in-flight -- RestoreSession
545
645
  * will have placed a non-null (but expired) token in state, causing the guard
546
646
  * to pass even though the subsequent config/refresh requests will fail and
547
647
  * dispatch Logout. Waiting for ready$ guarantees the auth state is settled.
648
+ *
649
+ * Signed in is not enough: the console is granted, and the server says to whom
650
+ * (ConsoleAccessService). An account it refuses -- a customer who registered on the
651
+ * public site -- is signed out of this sign-in, on the server as well as here, and
652
+ * sent to the login page, which says why. A failure to ask is not a refusal: the console opens, and every request it
653
+ * makes is still decided by the server.
548
654
  */
549
655
  declare const authGuard: CanActivateFn;
550
656
 
@@ -591,6 +697,26 @@ declare const authInterceptor: HttpInterceptorFn;
591
697
  * (real auth failure). Transient errors (network blips, 5xx,
592
698
  * CORS, timeouts) propagate without clearing the session so a
593
699
  * momentary refresh failure does not kill a valid login.
700
+ *
701
+ * A token the server REFUSED (2026-09-26). A caller answering a 401
702
+ * passes the access token that was refused. Until then the lock body
703
+ * handed back the cached token whenever its expiry looked fresh --
704
+ * which a revoked token's does: a session ended on the server (a
705
+ * sign-out everywhere from another device, a password change) was
706
+ * retried with the same dead token and never refreshed, so the client
707
+ * stayed signed in, and in its calls, until the token neared expiry
708
+ * (measured: a 1:1 call kept for the whole 30 s window, every request
709
+ * answered 401). Now the lock body re-reads the SHARED copy
710
+ * (`localStorage`, which every tab's `SetTokens` writes synchronously;
711
+ * this tab's state only follows a sibling's write when its `storage`
712
+ * event arrives):
713
+ * - a different access token there -> another tab (or an earlier
714
+ * refresh here) already rotated past the refused one: use it, no
715
+ * request;
716
+ * - the same one -> the server refused this token: refresh, with the
717
+ * newest refresh token -- the shared one, so a rotation a sibling
718
+ * made is never replayed with the token it consumed. A 401 there
719
+ * is the session's end: `Logout`.
594
720
  */
595
721
  declare class AuthRefreshCoordinator {
596
722
  private readonly api;
@@ -601,10 +727,16 @@ declare class AuthRefreshCoordinator {
601
727
  * Return the in-flight refresh Observable, or spawn a new one
602
728
  * with the supplied refresh-token value.
603
729
  *
730
+ * `refusedAccessToken`: the access token the server just answered
731
+ * 401 to, when the caller is answering one. With it, the lock body
732
+ * refreshes unless the shared copy already holds another token;
733
+ * without it (a preemptive refresh), a fresh-looking cached token
734
+ * is handed back as before.
735
+ *
604
736
  * Callers subscribe via `switchMap` / `firstValueFrom` to retry
605
737
  * their original request with the resolved access token.
606
738
  */
607
- refresh(refreshToken: string): Observable<string>;
739
+ refresh(refreshToken: string, refusedAccessToken?: string | null): Observable<string>;
608
740
  /**
609
741
  * Run the refresh body inside `navigator.locks` when the API is
610
742
  * available; degrade to direct invocation otherwise. The lock
@@ -613,22 +745,34 @@ declare class AuthRefreshCoordinator {
613
745
  */
614
746
  private refreshWithCrossTabLock;
615
747
  /**
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.
748
+ * Lock body. Re-reads the shared copy first (see the class note).
749
+ *
750
+ * Answering a refusal: a shared access token other than the refused
751
+ * one is a rotation already made -- taken, and mirrored into this
752
+ * tab's state if its `storage` event has not arrived yet. Otherwise,
753
+ * or for a preemptive caller whose cached token no longer looks
754
+ * fresh, the HTTP refresh, with the newest refresh token: shared,
755
+ * then state, then the caller's.
621
756
  */
622
757
  private executeRefresh;
623
758
  /**
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.
759
+ * The session as the tabs share it, or null when there is none to
760
+ * read: no storage, nothing stored (signed out), or a value that is
761
+ * not a complete token set.
762
+ */
763
+ private readShared;
764
+ /**
765
+ * For a preemptive caller only: return the current access token when
766
+ * its expiry is more than `FRESH_TOKEN_GRACE_MS` milliseconds away --
767
+ * signalling that some other tab refreshed for us. The shared copy
768
+ * first, which a sibling's rotation reaches before this tab's state
769
+ * does (mirrored here when it is newer); this tab's state when there
770
+ * is none. Null otherwise. Never asked for a token the server
771
+ * refused: a refused token's expiry looks just as fresh.
628
772
  */
629
773
  private tryConsumeFreshState;
630
774
  static ɵfac: i0.ɵɵFactoryDeclaration<AuthRefreshCoordinator, never>;
631
- static ɵprov: i0.ɵɵInjectableDeclaration<AuthRefreshCoordinator>;
775
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
632
776
  }
633
777
 
634
778
  /**
@@ -661,16 +805,366 @@ declare class CrossTabAuthSyncService {
661
805
  start(): void;
662
806
  private readonly handleStorageEvent;
663
807
  static ɵfac: i0.ɵɵFactoryDeclaration<CrossTabAuthSyncService, never>;
664
- static ɵprov: i0.ɵɵInjectableDeclaration<CrossTabAuthSyncService>;
808
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
809
+ }
810
+
811
+ /**
812
+ * The elevation state of the signed-in session: the wire shape of
813
+ * `GET /auth/elevation`, returned again by `POST` on a grant.
814
+ *
815
+ * Elevation is state, not a role. It lasts a fixed time from the grant, ends
816
+ * when the admin panel is closed or reloaded, and the client derives nothing
817
+ * about it from mode bits or membership: this record and the node resource's
818
+ * capability flags are the two readings of ONE server-side decision, and the
819
+ * client keeps no third.
820
+ */
821
+ interface ElevationState {
822
+ /** Whether this session is elevated right now. */
823
+ elevated: boolean;
824
+ /** When the current elevation ends (ISO 8601); absent or null when not elevated. */
825
+ expiresAt?: string | null;
826
+ /** When the current elevation was granted (ISO 8601); absent or null when not elevated. */
827
+ grantedAt?: string | null;
828
+ /** How the LAST elevation of this session ended. Rendered by the prompt before it asks. */
829
+ ended: ElevationEnded;
830
+ /** The lifetime a grant made now would get, in seconds (the setting, clamped to the ceiling). */
831
+ lifetimeSeconds: number;
832
+ /** The most any elevation can last, fixed in code. */
833
+ lifetimeCeilingSeconds: number;
834
+ /** Whether the CURRENT elevation was confirmed by a second factor; absent or null when not elevated. */
835
+ factorConfirmed?: boolean | null;
836
+ /** Whether a grant made now must carry a code from the person's own authenticator. */
837
+ mfaRequired: boolean;
838
+ /** What the prompt shows before asking; two of the codes mean this account cannot elevate yet. */
839
+ warnings: ElevationWarning[];
840
+ }
841
+ /**
842
+ * `closed` is the beacon (the panel was closed or reloaded), `dropped_by_user`
843
+ * the explicit drop, `refresh_from_other_pair` the server's refresh tripwire,
844
+ * `never` a session that has not elevated yet.
845
+ */
846
+ type ElevationEndedReason = 'expired' | 'closed' | 'dropped_by_user' | 'refresh_from_other_pair' | 'never';
847
+ interface ElevationEnded {
848
+ reason: ElevationEndedReason;
849
+ /** ISO 8601, null for `never`. */
850
+ at: string | null;
851
+ }
852
+ /**
853
+ * `elevation.password_not_set` and `elevation.factor_missing` mean this
854
+ * account cannot elevate (it has no password, or no confirmed factor, of its
855
+ * own) and the message says which;
856
+ * `elevation.mfa_off` is the recommendation carried while the switch is off.
857
+ */
858
+ interface ElevationWarning {
859
+ /** The three the server documents today; the client renders any it does not know as text. */
860
+ code: string;
861
+ message: string;
862
+ }
863
+ declare const ELEVATION_WARNING_PASSWORD_NOT_SET = "elevation.password_not_set";
864
+ declare const ELEVATION_WARNING_FACTOR_MISSING = "elevation.factor_missing";
865
+ declare const ELEVATION_WARNING_MFA_OFF = "elevation.mfa_off";
866
+ /** Why the elevation of a session changed, for whoever refetches on it. */
867
+ type ElevationChangeKind = 'granted' | 'dropped' | 'expired' | 'observed';
868
+ interface ElevationChange {
869
+ kind: ElevationChangeKind;
870
+ state: ElevationState;
871
+ }
872
+ /** The two reasons the client sends when it drops an elevation itself. */
873
+ type ElevationDropReason = 'dropped_by_user' | 'closed';
874
+
875
+ /**
876
+ * What the elevation prompt is told when it opens.
877
+ *
878
+ * `state` is the server's answer at the moment of asking -- `ended.reason` is
879
+ * rendered first (a person asked for a password again after F5
880
+ * with no explanation reads it as a fault), `warnings` may say this
881
+ * account cannot elevate yet, `mfaRequired` decides whether a code field
882
+ * shows from the start.
883
+ *
884
+ * `refusal` is the server's own sentence for the action that was refused
885
+ * ("Permission denied: cannot write '/docs/...'"), when a 403 opened the
886
+ * prompt; absent when a disabled control offered elevation instead.
887
+ */
888
+ interface ElevationPromptRequest {
889
+ state: ElevationState;
890
+ refusal?: string;
891
+ }
892
+ /**
893
+ * The prompt itself is a dialog, and dialogs are the kit's -- a layer above
894
+ * this one. Core owns the state, the interceptor and the decision to ask; the
895
+ * application binds this port to whatever renders the question. Same shape as
896
+ * `CURRENT_SECTION`: core declares the little it needs, the composition root
897
+ * provides it.
898
+ *
899
+ * Resolves to true when the session is elevated by the time the dialog closes,
900
+ * false when the person declined or could not. The port does not retry the
901
+ * refused action; the interceptor does, on true.
902
+ */
903
+ interface ElevationPromptPort {
904
+ open(request: ElevationPromptRequest): Observable<boolean>;
665
905
  }
906
+ /**
907
+ * Optional by design: with nothing bound, a 403 stays a 403 and the state
908
+ * service still answers -- an application without an admin panel has no
909
+ * prompt to show and loses nothing.
910
+ */
911
+ declare const ELEVATION_PROMPT: InjectionToken<ElevationPromptPort>;
912
+ /**
913
+ * Told when an action a person took was refused for want of elevation: a
914
+ * write (not a read, not a background request) answered by a stamped 403.
915
+ *
916
+ * It must not open the prompt. The prompt opens only when the person asks for
917
+ * it -- the application shows the refusal and offers elevation as something to
918
+ * click (a notice with an Elevate button), and the click is what calls
919
+ * {@link ElevationService.offerFor}. A refused action is not repeated by
920
+ * itself after a grant: the person repeats it.
921
+ *
922
+ * `refusal` is the server's sentence for the refused action, already made
923
+ * readable by the error service.
924
+ */
925
+ interface ElevationNoticePort {
926
+ refused(refusal: string): void;
927
+ }
928
+ /** Optional, like {@link ELEVATION_PROMPT}: with nothing bound, a refusal is only a 403. */
929
+ declare const ELEVATION_NOTICE: InjectionToken<ElevationNoticePort>;
930
+
931
+ /**
932
+ * Set on a request that must never open the elevation prompt: the elevation
933
+ * endpoint's own calls (a wrong password is a 403 and is not a refused action),
934
+ * and any caller that would rather receive its 403 and decide for itself.
935
+ */
936
+ declare const BYPASS_ELEVATION: HttpContextToken<boolean>;
937
+ /**
938
+ * The elevation state of this session, read from ONE place: the server.
939
+ *
940
+ * The elevation client requirement, in one service. The state endpoint is the only
941
+ * source -- `elevated`, `expiresAt`, how the last elevation ended -- and every
942
+ * change of it is announced on `changes$` so the Explorer listing can refetch
943
+ * (its capability flags are the server's reading of the same state, computed
944
+ * when the listing was fetched, and a `write: true` fetched while elevated
945
+ * must not outlive the elevation).
946
+ *
947
+ * **Expiry is driven by the READ, not by a timer.** An elevation ends because
948
+ * time passed, and time is not an event; a client that waits to be told has
949
+ * nothing to wait for when the machine was asleep at the moment. So `state()`
950
+ * does the arithmetic every time it is asked, and an answer whose `expiresAt`
951
+ * has gone by reports expired without a request. The timer below still fires
952
+ * at `expiresAt` and the tab still re-asks when it comes back into view -- both
953
+ * are how the SERVER's answer is refreshed, which is a different thing from
954
+ * knowing this one has run out.
955
+ *
956
+ * This is not a second decider. The server said "elevated until T"; after T,
957
+ * continuing to report `elevated: true` asserts something it never said.
958
+ *
959
+ * The prompt is not here: dialogs are the kit's, a layer above core. The
960
+ * service asks the `ELEVATION_PROMPT` port and shares ONE in-flight prompt, so
961
+ * a burst of refused requests (a multi-select delete is one 403 per node)
962
+ * opens one dialog and every caller waits on the same answer.
963
+ */
964
+ /**
965
+ * The header a 403 refused for want of elevation carries, with the gate's name
966
+ * as its value. The server stamps it where the gate refused; the interceptor
967
+ * prompts on it and on nothing else. There is no list of gated URLs on this
968
+ * side any more: the one that stood here lagged the server by every route
969
+ * whose refusal came from the VFS permission service under another module's
970
+ * path (media, document and word templates, content distribution).
971
+ */
972
+ declare const ELEVATION_REQUIRED_HEADER = "X-Elevation-Required";
973
+ /**
974
+ * Set on a request no person asked for: a realtime token, a preferences sync, a
975
+ * poll. Its refusal is never announced -- not by a dialog and not by a notice --
976
+ * because nobody is waiting on it and nobody can act on it. The caller still
977
+ * receives the 403 and decides what its own state shows.
978
+ */
979
+ declare const BACKGROUND_REQUEST: HttpContextToken<boolean>;
980
+ /**
981
+ * Whether a failed request was refused for want of elevation: a 403 carrying
982
+ * the server's stamp. The one test every reader of a refusal uses -- the
983
+ * interceptor, a page's inline state, the error text -- so they cannot disagree.
984
+ */
985
+ declare function isElevationRefusal(err: unknown): boolean;
986
+ declare class ElevationService {
987
+ private readonly http;
988
+ private readonly store;
989
+ private readonly prompt;
990
+ private readonly destroyRef;
991
+ /** The last answer the server gave, exactly as it gave it. */
992
+ private readonly served;
993
+ /**
994
+ * The state as it stands NOW; null until the first read from the server.
995
+ *
996
+ * Deliberately NOT a `computed`. A cached derivation is only recomputed
997
+ * when one of its dependencies changes, and nothing changes when an
998
+ * elevation simply runs out -- so it would keep answering `elevated: true`
999
+ * past the moment the server itself named, for as long as the machine
1000
+ * happened to be asleep.
1001
+ *
1002
+ * Reading past `expiresAt` also demotes the STORED answer. The value
1003
+ * returned here is already correct without that write; the write is only
1004
+ * how cached readers (a `computed`, an OnPush template) find out.
1005
+ */
1006
+ readonly state: () => ElevationState | null;
1007
+ /** Convenience over `state`: false until the server has said otherwise. */
1008
+ readonly elevated: () => boolean;
1009
+ /** `expiresAt` as a Date, null when not elevated. */
1010
+ readonly expiresAt: () => Date | null;
1011
+ /**
1012
+ * Every change of elevation state: a grant, a drop (by this person, by the
1013
+ * beacon, by the tripwire), an expiry, or an observed change of a live
1014
+ * grant's `expiresAt`. A refresh that reports the same state emits nothing,
1015
+ * so subscribers that refetch a listing on it never refetch for no reason.
1016
+ */
1017
+ readonly changes$: Subject<ElevationChange>;
1018
+ private expiryTimer;
1019
+ private pendingPrompt;
1020
+ private expiryAnnouncePending;
1021
+ private lastReconcileAt;
1022
+ constructor();
1023
+ /** The manifest carries the URL; without it the service is inert, not broken. */
1024
+ get available(): boolean;
1025
+ private get url();
1026
+ /**
1027
+ * Whether a URL belongs to THIS API: a relative one under the manifest's
1028
+ * `apiBase`, or an absolute one on this origin under it. The interceptor
1029
+ * acts on a stamped refusal from here and from nowhere else -- a third
1030
+ * party answering 403 with a header of the same name is not an invitation
1031
+ * to elevate this session.
1032
+ */
1033
+ isThisApi(url: string): boolean;
1034
+ /** Ask the server. Applies the answer and announces it when it differs. */
1035
+ refresh(): Observable<ElevationState>;
1036
+ /**
1037
+ * Elevate with the person's own password and, when the switch is on, a
1038
+ * code from their own authenticator. Errors are the dialog's to branch on:
1039
+ * 403 not a member of the admin group, wrong password, or wrong / replayed
1040
+ * code, 428 a code is required, 409 this account cannot elevate yet, 429
1041
+ * throttled. They pass through
1042
+ * untouched, and never open the prompt (BYPASS_ELEVATION).
1043
+ */
1044
+ elevate(password: string, code?: string): Observable<ElevationState>;
1045
+ /** Drop the elevation now. Idempotent on the server: nothing live is still a 204. */
1046
+ drop(reason?: ElevationDropReason): Observable<ElevationState>;
1047
+ /**
1048
+ * What a person's explicit request for elevation calls -- an Elevate button,
1049
+ * or a control that already knows its action needs elevation: ask the
1050
+ * server whether this session is elevated, and only when it is not, open
1051
+ * the prompt. Nothing calls it on a response by itself (the interceptor
1052
+ * only tells {@link ELEVATION_NOTICE}). Elevated already is false, no
1053
+ * prompt: elevation would change nothing.
1054
+ */
1055
+ offerFor(refusal?: string): Observable<boolean>;
1056
+ /**
1057
+ * Open the prompt, or join the one already open. Resolves to whether the
1058
+ * session is elevated once the dialog closes.
1059
+ */
1060
+ requestElevation(request: ElevationPromptRequest): Observable<boolean>;
1061
+ private options;
1062
+ private inert;
1063
+ /**
1064
+ * Store the answer, schedule the expiry read, announce a change.
1065
+ *
1066
+ * `explicit` names the change when the caller knows it (a grant). For a
1067
+ * plain refresh the kind is derived from the two states: elevated -> not
1068
+ * is `expired` when the server says so and `dropped` otherwise (the person,
1069
+ * another tab, the beacon, the tripwire); not -> elevated is a grant seen
1070
+ * from another tab; a live grant whose `expiresAt` moved is `observed`.
1071
+ *
1072
+ * `prev` is read RAW rather than through `state()`: the demotion is not
1073
+ * wanted here (this answer supersedes the stored one either way), and a
1074
+ * read that demoted would queue an expiry announcement the server's own
1075
+ * answer is about to make.
1076
+ */
1077
+ private apply;
1078
+ private kindOf;
1079
+ /**
1080
+ * Tell the listings that this session's elevation ran out, once, and NOT
1081
+ * from inside the read that noticed: a subscriber refetches on this, and a
1082
+ * refetch started in the middle of a change detection pass is a write
1083
+ * during a read. The microtask runs at the end of the current task, before
1084
+ * anything can paint.
1085
+ *
1086
+ * A server answer arriving first clears the flag, so the announcement is
1087
+ * made by whichever of the two got there first, never by both. Other tabs
1088
+ * are NOT told: each one reaches the same conclusion from the same
1089
+ * `expiresAt` on its own read, so the storage write would only buy a
1090
+ * thundering herd of GETs.
1091
+ */
1092
+ private announceExpiryLater;
1093
+ /**
1094
+ * Ask the server again at `expiresAt` -- an OPTIMISATION, not the
1095
+ * mechanism. `state()` already reports an elapsed grant as expired without
1096
+ * being told, so a timer that a sleeping machine swallows, or that a
1097
+ * background tab throttles to once a minute, now costs freshness rather
1098
+ * than correctness. What it buys is the listing refetch happening at the
1099
+ * moment of expiry instead of at the next read.
1100
+ *
1101
+ * Do not flip a local flag here: the lifetime counts from the grant on the
1102
+ * server's clock, and if the server still says elevated at that moment
1103
+ * (skew), the honest thing is to ask again shortly.
1104
+ */
1105
+ private schedule;
1106
+ /**
1107
+ * One more attempt, then stop. Giving up is affordable now and was not
1108
+ * before: the state a failed read leaves behind is still read through the
1109
+ * arithmetic above, so an unreachable server cannot leave this tab
1110
+ * believing it is elevated. What is lost is only news from elsewhere -- a
1111
+ * grant or a drop in another session -- and that is what the tab picks up
1112
+ * when it next comes into view.
1113
+ */
1114
+ private scheduleRetry;
1115
+ private clearTimer;
1116
+ private announceToOtherTabs;
1117
+ private readonly onStorage;
1118
+ private readonly onFocus;
1119
+ private readonly onVisibilityChange;
1120
+ /**
1121
+ * The tab is back. Read first -- that alone ends an elevation that ran out
1122
+ * while it was away, with no request and no network to depend on -- then
1123
+ * ask the server for what the read cannot know: a grant or a drop made
1124
+ * elsewhere, or a lifetime the server moved.
1125
+ */
1126
+ private reconcile;
1127
+ static ɵfac: i0.ɵɵFactoryDeclaration<ElevationService, never>;
1128
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1129
+ }
1130
+
1131
+ /**
1132
+ * A refusal for want of elevation is a 403 the server stamps `X-Elevation-Required`
1133
+ * where its gate refused a member who has not elevated ({@link isElevationRefusal}).
1134
+ * This interceptor never opens the elevation prompt. The prompt opens only when a
1135
+ * person asks for it: an Elevate button, or an action whose control already knows it
1136
+ * needs elevation and offers it ({@link ElevationService.offerFor}).
1137
+ *
1138
+ * What it does with a stamped 403:
1139
+ * - On a write a person made (POST, PUT, PATCH, DELETE without
1140
+ * {@link BACKGROUND_REQUEST}): tells {@link ELEVATION_NOTICE}, which shows the
1141
+ * refusal with an Elevate button. The request is not held and not repeated; the
1142
+ * caller receives the 403 at once, and after elevating the person repeats the action.
1143
+ * - On a read: nothing. Reads are what a page makes as it loads and what polls make in
1144
+ * the background, and the page shows its own state for a refused one -- an inline
1145
+ * "needs an elevated session" with an Elevate button, or the feature hidden.
1146
+ * - On a background request: nothing. Nobody is waiting on it.
1147
+ *
1148
+ * Until 2026-10-08 any stamped 403 opened the prompt, so the setup reads of almost every
1149
+ * console page, and a realtime token at every load, opened it for an administrator who
1150
+ * had not elevated: 13 of 15 pages on a menu click, 15 of 15 on a reload (measured).
1151
+ *
1152
+ * The elevation endpoint's own calls carry {@link BYPASS_ELEVATION}: a wrong password is a
1153
+ * 403 and is not a refused action. A stamped 403 from anywhere but this API is ignored:
1154
+ * the header is the server's word, and only this server's counts.
1155
+ */
1156
+ declare const elevationInterceptor: HttpInterceptorFn;
666
1157
 
667
1158
  declare class LoginComponent {
668
1159
  identifier: string;
669
1160
  password: string;
670
- loading: boolean;
671
- error: string | null;
1161
+ readonly loading: i0.WritableSignal<boolean>;
1162
+ readonly error: i0.WritableSignal<string | null>;
672
1163
  private readonly store;
673
1164
  private readonly router;
1165
+ /** Shown when the guard signed an account out because the console is not granted to it. */
1166
+ static readonly NO_CONSOLE_ACCESS = "This account has no access to the console.";
1167
+ constructor();
674
1168
  submit(): void;
675
1169
  static ɵfac: i0.ɵɵFactoryDeclaration<LoginComponent, never>;
676
1170
  static ɵcmp: i0.ɵɵComponentDeclaration<LoginComponent, "coolms-admin-login", never, {}, {}, never, never, true, never>;
@@ -684,10 +1178,10 @@ declare class AppInitService {
684
1178
  private readonly prefs;
685
1179
  private readonly crossTabSync;
686
1180
  private readonly refreshCoordinator;
687
- /** The only hardcoded URL — everything else comes from the manifest. */
1181
+ /** The only hardcoded URL -- everything else comes from the manifest. */
688
1182
  private readonly configUrl;
689
1183
  /**
690
- * Emits (once) when load() has fully completed — tokens settled, manifest
1184
+ * Emits (once) when load() has fully completed -- tokens settled, manifest
691
1185
  * set (or swallowed on error). authGuard subscribes to this so it never
692
1186
  * evaluates isAuthenticated against a partially-restored stale token.
693
1187
  */
@@ -711,7 +1205,64 @@ declare class AppInitService {
711
1205
  /** GET an authed endpoint with an explicit Bearer header, bypassing the interceptor. */
712
1206
  private probe;
713
1207
  static ɵfac: i0.ɵɵFactoryDeclaration<AppInitService, never>;
714
- static ɵprov: i0.ɵɵInjectableDeclaration<AppInitService>;
1208
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1209
+ }
1210
+
1211
+ /** What the server said about this sign-in and the console. */
1212
+ type ConsoleAccess = 'granted' | 'refused' | 'unknown';
1213
+ /**
1214
+ * Whether the signed-in account may use the console, and the console's endpoint
1215
+ * map when it may.
1216
+ *
1217
+ * The server decides: `GET /api/v1/admin/manifest` answers the map. Since 2026-10-06
1218
+ * (Dmitry: "no host groups"; access is decided by module groups alone) it answers every
1219
+ * signed-in account: the map grants nothing, each call it names is decided by its own
1220
+ * route, and an account in no group sees the admin with an empty menu. A server from
1221
+ * before that answers only at `/api/v1/console/manifest`, to the `console` group, and
1222
+ * 403 to anyone else -- there a refused sign-in is still signed out; this asks the new
1223
+ * address first and the old one only on its 404. The public theme config
1224
+ * read before sign-in carries only what the sign-in page needs; the rest arrives
1225
+ * here, once per sign-in, and is merged into the app config key by key: a key the
1226
+ * console's map carries replaces the public one whole, so the console's copy of a
1227
+ * section is always the complete one.
1228
+ *
1229
+ * `unknown` is a failure to ask (the network, a 5xx), not an answer: it is not kept,
1230
+ * so while the endpoint fails every navigation asks again.
1231
+ *
1232
+ * !! An answer belongs to the sign-in it was asked for. A sign-in, a sign-out or a
1233
+ * restored session starts a new generation; an answer that arrives for a generation
1234
+ * that is gone is kept for nobody -- A's late 403 must not sign B out -- and whoever
1235
+ * is waiting on it is answered for the sign-in there is now.
1236
+ *
1237
+ * !! With nobody signed in there is nothing to ask, and nothing is asked. Without
1238
+ * that, the re-ask after a sign-out went out with no token, its 401 made the
1239
+ * interceptor dispatch Logout, which started another generation, which re-asked:
1240
+ * a loop for as long as the tab lived (found in review).
1241
+ */
1242
+ declare class ConsoleAccessService {
1243
+ /** The admin manifest's URL: fixed, like the theme config's, because nothing is known before it. */
1244
+ static readonly URL = "/api/v1/admin/manifest";
1245
+ /** The address before 2026-10-06, asked only when a server older than that answers 404 at {@link URL}. */
1246
+ static readonly LEGACY_URL = "/api/v1/console/manifest";
1247
+ /** The login page's query parameter value for "signed out: no console access". */
1248
+ static readonly REFUSED_REASON = "no-console-access";
1249
+ private readonly http;
1250
+ private readonly store;
1251
+ private answer;
1252
+ private pending;
1253
+ private generation;
1254
+ constructor();
1255
+ /** Ask once per sign-in; the manifest is merged into the app config when granted. */
1256
+ ensure(): Promise<ConsoleAccess>;
1257
+ /**
1258
+ * Sign a refused account out: this sign-in ends on the server (its refresh token
1259
+ * with it), as the account menu's sign-out does, and then in this browser --
1260
+ * whatever the server answered, so a refused request cannot leave it signed in.
1261
+ */
1262
+ signOut(): Observable<void>;
1263
+ private merge;
1264
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleAccessService, never>;
1265
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
715
1266
  }
716
1267
 
717
1268
  /**
@@ -749,7 +1300,7 @@ interface LayoutHeaderAction {
749
1300
  disabled?: boolean;
750
1301
  /**
751
1302
  * Optional capability gate evaluated FE-side against runtime state
752
- * (e.g. `administer` / `delete`) — the config declares WHICH actions
1303
+ * (e.g. `administer` / `delete`) -- the config declares WHICH actions
753
1304
  * exist + their gate; the FE decides if THIS user/row qualifies. Absent
754
1305
  * = always shown. Used by detail pages whose actions are config-driven
755
1306
  * (see `web:section-detail`).
@@ -764,11 +1315,11 @@ interface LayoutHeaderAction {
764
1315
  * evaluator as a NaviGraph toolbar node, applied
765
1316
  * by {@link LayoutActionsService}:
766
1317
  *
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.
1318
+ * showWhen -- the action does not APPLY, so it is absent.
1319
+ * disabledWhen -- temporarily UNAVAILABLE: stays put and greys out.
1320
+ * busyWhen -- the action is RUNNING: greys out too, and may relabel.
1321
+ * busyLabel -- label shown while `busyWhen` holds ("Saving...").
1322
+ * activeWhen -- pressed/active styling.
772
1323
  *
773
1324
  * Two carriers (a layout config and a NaviGraph tree) but ONE vocabulary:
774
1325
  * a page that moves between them should not have to relearn how to say
@@ -808,7 +1359,7 @@ interface LayoutConfig {
808
1359
  headerActions?: LayoutHeaderAction[];
809
1360
  /**
810
1361
  * Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)
811
- * — primary / destructive actions kept out of the cramped header.
1362
+ * -- primary / destructive actions kept out of the cramped header.
812
1363
  * Same shape as `headerActions`; gated per-item via `requires`.
813
1364
  */
814
1365
  footerActions?: LayoutHeaderAction[];
@@ -831,7 +1382,8 @@ interface DatagridConfig {
831
1382
  * Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.
832
1383
  *
833
1384
  * Responses are cached for the lifetime of the service (Map + shareReplay(1))
834
- * so repeated calls never re-fetch the same resource.
1385
+ * so repeated calls never re-fetch the same resource. A failed read is dropped
1386
+ * from the cache, so the next call asks the server again.
835
1387
  *
836
1388
  * configBase is read from the API manifest (manifest.configBase) and falls
837
1389
  * back to '/api/v1/config' when the manifest is not yet loaded.
@@ -847,7 +1399,7 @@ declare class ConfigService {
847
1399
  datagrid(id: string): Observable<DatagridConfig>;
848
1400
  private fetch;
849
1401
  static ɵfac: i0.ɵɵFactoryDeclaration<ConfigService, never>;
850
- static ɵprov: i0.ɵɵInjectableDeclaration<ConfigService>;
1402
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
851
1403
  }
852
1404
 
853
1405
  declare class SetAppConfig {
@@ -861,16 +1413,18 @@ interface AppConfigStateModel {
861
1413
  }
862
1414
  declare class AppConfigState {
863
1415
  set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void;
1416
+ /** The whole config as last set: the public theme config, with the console's manifest once granted. */
1417
+ static config(state: AppConfigStateModel): ThemeConfigResponse | null;
864
1418
  static manifest(state: AppConfigStateModel): ApiManifest | null;
865
1419
  /**
866
- * F.7 viewer manifest. Null when the backend didn't ship a viewers
1420
+ * The viewer manifest. Null when the backend didn't ship a viewers
867
1421
  * section (no tagged providers); consumers should fall back to a
868
1422
  * "no preview" surface.
869
1423
  */
870
1424
  static viewers(state: AppConfigStateModel): ViewerApiManifest | null;
871
1425
  static loaded(state: AppConfigStateModel): boolean;
872
1426
  static ɵfac: i0.ɵɵFactoryDeclaration<AppConfigState, never>;
873
- static ɵprov: i0.ɵɵInjectableDeclaration<AppConfigState>;
1427
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
874
1428
  }
875
1429
 
876
1430
  /**
@@ -887,19 +1441,19 @@ declare class ErrorHandlerService {
887
1441
  humanize(err: unknown): string;
888
1442
  private statusMessage;
889
1443
  static ɵfac: i0.ɵɵFactoryDeclaration<ErrorHandlerService, never>;
890
- static ɵprov: i0.ɵɵInjectableDeclaration<ErrorHandlerService>;
1444
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
891
1445
  }
892
1446
 
893
1447
  /** What the user PICKED. `system` defers to the OS. */
894
1448
  type ThemeChoice = 'light' | 'dark' | 'system';
895
- /** What that resolves to right now — the only two the stylesheet knows. */
1449
+ /** What that resolves to right now -- the only two the stylesheet knows. */
896
1450
  type ResolvedTheme = 'light' | 'dark';
897
1451
  /**
898
1452
  * Applies the user's theme by setting `data-theme` on the document element.
899
1453
  *
900
1454
  * The stylesheet does the rest: `:root[data-theme='dark']` re-points the
901
1455
  * `--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
1456
+ * Nothing here knows a single colour, which is the point -- a second
903
1457
  * theme is a block of tokens, not a code change.
904
1458
  *
905
1459
  * Follows the shape of UserCalendarPreferencesService and
@@ -923,7 +1477,7 @@ declare class ThemeService {
923
1477
  readonly platformAccent: i0.Signal<string | null>;
924
1478
  /**
925
1479
  * The colour actually painted: personal override, else the deployment's,
926
- * else null — which leaves the stylesheet's own accent in place.
1480
+ * else null -- which leaves the stylesheet's own accent in place.
927
1481
  *
928
1482
  * Three rungs and each is a real state. A user who has chosen nothing is
929
1483
  * NOT the same as one who chose the deployment's colour: clear the
@@ -935,13 +1489,13 @@ declare class ThemeService {
935
1489
  constructor();
936
1490
  /**
937
1491
  * 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
1492
+ * safe for anonymous users -- a failure leaves the cached/system value in
939
1493
  * place rather than forcing light, so the login screen still matches the OS.
940
1494
  */
941
1495
  ensureLoaded(): Observable<ThemeChoice>;
942
1496
  /**
943
1497
  * 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 —
1498
+ * re-themes on the spot. Ignores anything that is not a known choice --
945
1499
  * the section PATCH returns the whole merged bag, not just this field.
946
1500
  */
947
1501
  update(value: unknown): void;
@@ -955,14 +1509,14 @@ declare class ThemeService {
955
1509
  /**
956
1510
  * The manifest is loaded at bootstrap, well before the authenticated shell
957
1511
  * 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
1512
+ * A malformed value is ignored rather than applied -- the container refuses
959
1513
  * to build with one, but the manifest crosses the network all the same.
960
1514
  */
961
1515
  private readPlatformAccent;
962
1516
  private setAccent;
963
1517
  /**
964
1518
  * Writes the override onto the document element, where an inline style
965
- * outranks every stylesheet rule — including the dark block — so one
1519
+ * outranks every stylesheet rule -- including the dark block -- so one
966
1520
  * declaration re-points the accent in both themes.
967
1521
  *
968
1522
  * Only --cms-accent is set, not the -hover/-light/-text members of the
@@ -976,7 +1530,7 @@ declare class ThemeService {
976
1530
  private static readCache;
977
1531
  private static readAccentCache;
978
1532
  static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
979
- static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
1533
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
980
1534
  }
981
1535
 
982
1536
  /**
@@ -988,7 +1542,7 @@ declare class ThemeService {
988
1542
  * A spinner says "something is happening" and nothing else; every product has
989
1543
  * one and none of them are yours. The bang is already the one glyph in the
990
1544
  * 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.
1545
+ * software is thinking -- for free, in the place a user is already looking.
992
1546
  *
993
1547
  * ## Why one component rather than a snippet per surface
994
1548
  *
@@ -999,7 +1553,7 @@ declare class ThemeService {
999
1553
  *
1000
1554
  * ## The animation
1001
1555
  *
1002
- * The stem wipes upward and the dot lands after it, on a loop — the shape being
1556
+ * The stem wipes upward and the dot lands after it, on a loop -- the shape being
1003
1557
  * WRITTEN rather than spun. It is deliberately calm: a loader is background
1004
1558
  * furniture, and anything with a hard beat gets irritating on the third viewing.
1005
1559
  * Under `prefers-reduced-motion` it holds still and only breathes, because
@@ -1017,7 +1571,7 @@ declare class CmsLoaderComponent {
1017
1571
  readonly inline: i0.InputSignal<boolean>;
1018
1572
  /**
1019
1573
  * 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
1574
+ * than once per page -- two loaders sharing an id makes the second one clip
1021
1575
  * against the first one's rectangle and stop animating.
1022
1576
  */
1023
1577
  protected readonly clipId: string;
@@ -1026,7 +1580,7 @@ declare class CmsLoaderComponent {
1026
1580
  }
1027
1581
 
1028
1582
  /**
1029
- * Unified user preferences service — all preference types share one
1583
+ * Unified user preferences service -- all preference types share one
1030
1584
  * localStorage key (`coolms_ui_prefs`) and one debounced server-sync pipeline.
1031
1585
  *
1032
1586
  * Key naming convention (stored as nested objects, referenced via dot-notation):
@@ -1147,7 +1701,7 @@ declare class UserPreferencesService {
1147
1701
  /** @deprecated Use setPanelState('media_panel_left', ...) instead */
1148
1702
  setMediaCollectionsPanel(width: number, collapsed: boolean): void;
1149
1703
  static ɵfac: i0.ɵɵFactoryDeclaration<UserPreferencesService, never>;
1150
- static ɵprov: i0.ɵɵInjectableDeclaration<UserPreferencesService>;
1704
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1151
1705
  }
1152
1706
 
1153
1707
  /**
@@ -1195,7 +1749,7 @@ declare class SidebarStateService {
1195
1749
  private readItemsPersisted;
1196
1750
  private persistItems;
1197
1751
  static ɵfac: i0.ɵɵFactoryDeclaration<SidebarStateService, never>;
1198
- static ɵprov: i0.ɵɵInjectableDeclaration<SidebarStateService>;
1752
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1199
1753
  }
1200
1754
 
1201
1755
  /**
@@ -1203,7 +1757,7 @@ declare class SidebarStateService {
1203
1757
  * what the picker emits via its `valueChange` output (and what the
1204
1758
  * template's persisted context carries). `label` is the primary
1205
1759
  * display line. `secondary` is an optional second line for
1206
- * disambiguation (email, slug, etc.) — the backend omits the key
1760
+ * disambiguation (email, slug, etc.) -- the backend omits the key
1207
1761
  * when there is nothing to show; receiving `null` from a custom
1208
1762
  * resolver is normalized to `undefined` here.
1209
1763
  */
@@ -1215,7 +1769,7 @@ interface EntitySearchResult {
1215
1769
  /**
1216
1770
  * HTTP client for the Phase 2a `/api/entity-search` endpoint.
1217
1771
  * Returns a stream of search results for the given entity FQCN.
1218
- * Permission filtering happens at the backend resolver layer —
1772
+ * Permission filtering happens at the backend resolver layer --
1219
1773
  * this service is a thin RPC.
1220
1774
  */
1221
1775
  declare class EntitySearchService {
@@ -1224,21 +1778,21 @@ declare class EntitySearchService {
1224
1778
  * @param entityType Fully-qualified PHP class name of the entity
1225
1779
  * being searched (e.g. the backend's User entity).
1226
1780
  * @param query Free-text search input. Empty string is
1227
- * valid — the backend returns the default
1781
+ * valid -- the backend returns the default
1228
1782
  * listing for that entity type.
1229
1783
  * @param limit Max rows to return; clamped server-side to
1230
1784
  * [1, 100].
1231
1785
  */
1232
1786
  search(entityType: string, query: string, limit?: number): Observable<EntitySearchResult[]>;
1233
1787
  static ɵfac: i0.ɵɵFactoryDeclaration<EntitySearchService, never>;
1234
- static ɵprov: i0.ɵɵInjectableDeclaration<EntitySearchService>;
1788
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1235
1789
  }
1236
1790
 
1237
1791
  /**
1238
1792
  * 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)
1793
+ * 'action.logout' -- dispatch Logout action
1794
+ * 'route' -- navigate via routerLink (default when target absent)
1795
+ * '_blank' -- open in a new tab (hrefOverride must be set)
1242
1796
  */
1243
1797
  type NaviGraphTarget = 'action.logout' | 'route' | '_blank' | (string & {});
1244
1798
  interface NaviGraphMeta {
@@ -1291,9 +1845,9 @@ declare class NaviGraphService {
1291
1845
  * Handle a nav-node click.
1292
1846
  *
1293
1847
  * 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
1848
+ * 'action.logout' -- dispatch Logout
1849
+ * '_blank' -- window.open (uses node.path as href)
1850
+ * default -- router.navigate with meta.routerLink or node.path
1297
1851
  */
1298
1852
  handleClick(node: NaviGraphNode): void;
1299
1853
  /**
@@ -1304,7 +1858,7 @@ declare class NaviGraphService {
1304
1858
  isVisible(node: NaviGraphNode, record: Record<string, unknown>): boolean;
1305
1859
  /**
1306
1860
  * Standalone `showWhen` evaluator for callers that don't have a
1307
- * full `NaviGraphNode` (e.g. a grid's row context menu — a
1861
+ * full `NaviGraphNode` (e.g. a grid's row context menu -- a
1308
1862
  * `rowAction.showWhen` predicate is evaluated against the row's
1309
1863
  * own projection). `null`/`undefined` predicates default to
1310
1864
  * visible, mirroring `isVisible`'s "no rule = always shown"
@@ -1325,7 +1879,7 @@ declare class NaviGraphService {
1325
1879
  */
1326
1880
  private buildTree;
1327
1881
  static ɵfac: i0.ɵɵFactoryDeclaration<NaviGraphService, never>;
1328
- static ɵprov: i0.ɵɵInjectableDeclaration<NaviGraphService>;
1882
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1329
1883
  }
1330
1884
 
1331
1885
  /**
@@ -1334,10 +1888,10 @@ declare class NaviGraphService {
1334
1888
  * Backed by a static Map so components registered at module load time
1335
1889
  * (e.g. in app.config.ts) are visible to all injected instances.
1336
1890
  *
1337
- * Usage — static (app.config.ts / bootstrap time):
1891
+ * Usage -- static (app.config.ts / bootstrap time):
1338
1892
  * ComponentRegistry.register('MediaLibraryPage', MediaLibraryPage);
1339
1893
  *
1340
- * Usage — injected (SlotComponent / runtime):
1894
+ * Usage -- injected (SlotComponent / runtime):
1341
1895
  * private readonly registry = inject(ComponentRegistry);
1342
1896
  * const cls = this.registry.get('MediaLibraryPage');
1343
1897
  */
@@ -1352,9 +1906,217 @@ declare class ComponentRegistry {
1352
1906
  has(key: string): boolean;
1353
1907
  keys(): string[];
1354
1908
  static ɵfac: i0.ɵɵFactoryDeclaration<ComponentRegistry, never>;
1355
- static ɵprov: i0.ɵɵInjectableDeclaration<ComponentRegistry>;
1909
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1356
1910
  }
1357
1911
 
1912
+ /**
1913
+ * `console` -- the administration host contract (the platform rule: hosts implement contracts, modules offer entries).
1914
+ *
1915
+ * A module contributes to the console with ONE object, its entry: data the
1916
+ * host reads, never a call the module makes into the host. Six lists, each
1917
+ * optional; an absent list means "nothing here", never an error. The host
1918
+ * assembles the entries (build time today, install time under federation),
1919
+ * refuses collisions by name, and activates only the modules the backend's
1920
+ * manifest says are installed.
1921
+ *
1922
+ * The version is MAJOR.MINOR: a minor adds an optional list or field, a major
1923
+ * changes or removes one. An entry declares the range it was written for
1924
+ * (`^1.2` -- major 1, minor at least 2), checked at build against this
1925
+ * constant and at install against the theme's declaration.
1926
+ */
1927
+ declare const CONSOLE_CONTRACT: {
1928
+ readonly name: "console";
1929
+ readonly version: "1.0";
1930
+ };
1931
+ interface ConsoleEntry {
1932
+ /** The backend module id, as `config/modules/<id>`. Activation is by this id. */
1933
+ readonly module: string;
1934
+ readonly contract: 'console';
1935
+ /** The console versions this entry was written for: a caret range over MAJOR.MINOR, e.g. '^1.0'. */
1936
+ readonly range: string;
1937
+ readonly routes?: readonly ConsoleRouteMount[];
1938
+ readonly bindings?: readonly ConsoleBinding[];
1939
+ readonly topbar?: readonly ConsoleTopbarItem[];
1940
+ readonly overlays?: readonly ConsoleOverlay[];
1941
+ readonly panels?: readonly ConsolePanel[];
1942
+ readonly states?: readonly Type<unknown>[];
1943
+ readonly providers?: readonly ConsoleProvision[];
1944
+ }
1945
+ /** What a route's `data` carries for the shell: the sidebar item to light, and whether the page owns its height. */
1946
+ interface ConsoleNavHint {
1947
+ readonly activeNav?: string;
1948
+ readonly fullHeight?: boolean;
1949
+ /** An intermediate crumb the top bar renders between Home and the page. */
1950
+ readonly breadcrumb?: {
1951
+ readonly label: string;
1952
+ readonly routerLink: string;
1953
+ };
1954
+ /** Which server layout the page renders, for the generic layout pages. */
1955
+ readonly layoutId?: string;
1956
+ }
1957
+ /**
1958
+ * A route mounted under the console's authenticated layout. The host adds
1959
+ * what a module may not decide: the layout component, `canActivate: [authGuard]`,
1960
+ * and a `canMatch` that answers whether the module is installed here. Routes
1961
+ * are lazy: a static component here would put every module's pages in `main`.
1962
+ */
1963
+ type ConsoleRouteMount = {
1964
+ readonly path: string;
1965
+ readonly children: () => Promise<Routes>;
1966
+ readonly nav?: ConsoleNavHint;
1967
+ readonly providers?: readonly Provider[];
1968
+ } | {
1969
+ readonly path: string;
1970
+ readonly component: () => Promise<Type<unknown>>;
1971
+ readonly nav?: ConsoleNavHint;
1972
+ readonly providers?: readonly Provider[];
1973
+ } | {
1974
+ readonly path: string;
1975
+ readonly redirectTo: string;
1976
+ };
1977
+ /** 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`). */
1978
+ interface ConsoleBinding {
1979
+ readonly name: string;
1980
+ readonly component: Type<unknown>;
1981
+ }
1982
+ /** A tile in the top bar's quick-access strip, rendered in ascending `order`. */
1983
+ interface ConsoleTopbarItem {
1984
+ readonly id: string;
1985
+ readonly order: number;
1986
+ readonly component: Type<unknown>;
1987
+ }
1988
+ /** Mounted once, inside the authenticated layout, outside the router outlet. */
1989
+ interface ConsoleOverlay {
1990
+ readonly id: string;
1991
+ readonly component: Type<unknown>;
1992
+ }
1993
+ /**
1994
+ * A dock panel the layout opens and closes: the host renders its toggle in
1995
+ * the top bar and the panel in the dock, and the panel talks back through
1996
+ * {@link ConsolePanelHost} -- not through outputs, which a slot cannot carry.
1997
+ */
1998
+ interface ConsolePanel {
1999
+ readonly id: string;
2000
+ readonly dock: 'bottom';
2001
+ /** The top-bar toggle: a label (the title), an icon name or a short text, and a key the host binds with Ctrl. */
2002
+ readonly toggle: {
2003
+ readonly label: string;
2004
+ readonly icon?: string;
2005
+ readonly text?: string;
2006
+ readonly key?: string;
2007
+ };
2008
+ readonly component: Type<unknown>;
2009
+ }
2010
+ /**
2011
+ * What a `provide*` line is: environment providers from a library's registry
2012
+ * (editor extensions, viewers, field widgets, file editors), or the
2013
+ * implementation of a single-implementation port, listed as such so the
2014
+ * assembly can refuse two modules implementing one port by name.
2015
+ */
2016
+ type ConsoleProvision = EnvironmentProviders | Provider | ConsolePortProvision;
2017
+ interface ConsolePortProvision {
2018
+ readonly port: InjectionToken<unknown>;
2019
+ readonly useExisting: Type<unknown>;
2020
+ }
2021
+ declare function isPortProvision(p: ConsoleProvision): p is ConsolePortProvision;
2022
+ /** The identity function with the type: an entry file exports data, nothing runs. */
2023
+ declare function consoleEntry(entry: ConsoleEntry): ConsoleEntry;
2024
+ /**
2025
+ * Whether a caret range over MAJOR.MINOR admits a version: same major, minor
2026
+ * at least the range's. Malformed input admits nothing -- a range that cannot
2027
+ * be read must refuse, never pass.
2028
+ */
2029
+ declare function satisfiesRange(range: string, version: string): boolean;
2030
+ /** The refusal an assembly raises: one message per collision, all of them, by name. */
2031
+ declare class ConsoleAssemblyError extends Error {
2032
+ readonly problems: readonly string[];
2033
+ constructor(problems: readonly string[]);
2034
+ }
2035
+ /**
2036
+ * The checks every assembly runs, in one place so the build-time spec, the
2037
+ * bootstrap and a future install-time loader agree: entry ranges against
2038
+ * this contract, and the collision rules of the six lists. Returns the
2039
+ * entries unchanged when nothing is wrong.
2040
+ */
2041
+ declare function assembleConsole(entries: readonly ConsoleEntry[]): readonly ConsoleEntry[];
2042
+
2043
+ /**
2044
+ * The host side of `console@1`: what a theme calls to turn the assembled
2045
+ * entries into routes, providers and the lists its shell renders.
2046
+ *
2047
+ * Activation is the backend's: `GET /app-config` carries `ui.modules`, the
2048
+ * modules whose console entry the installer matched against the theme's
2049
+ * declaration. A module in the bundle but not in that list is dormant -- no
2050
+ * route matches, no tile, no overlay; a module in the list but not in the
2051
+ * bundle is reported by name ({@link ConsoleActivation.missing}), never
2052
+ * silently absent.
2053
+ */
2054
+ /** Every entry this build assembled, after {@link assembleConsole}'s checks. */
2055
+ declare const CONSOLE_ENTRIES: InjectionToken<readonly ConsoleEntry[]>;
2056
+ /**
2057
+ * The port a dock panel talks back through: the layout provides one per
2058
+ * panel it mounts, so the panel never needs an output the slot cannot carry.
2059
+ */
2060
+ declare abstract class ConsolePanelHost {
2061
+ abstract close(): void;
2062
+ abstract maximize(on: boolean): void;
2063
+ }
2064
+ /** The console modules the manifest says are installed here. */
2065
+ declare function installedConsoleModules(manifest: ApiManifest | null): ReadonlySet<string>;
2066
+ declare class ConsoleActivation {
2067
+ private readonly entries;
2068
+ private readonly manifest;
2069
+ readonly installed: i0.Signal<ReadonlySet<string>>;
2070
+ /** The entries of installed modules, in assembly order. */
2071
+ readonly active: i0.Signal<ConsoleEntry[]>;
2072
+ readonly topbar: i0.Signal<readonly ConsoleTopbarItem[]>;
2073
+ readonly overlays: i0.Signal<readonly ConsoleOverlay[]>;
2074
+ readonly panels: i0.Signal<readonly ConsolePanel[]>;
2075
+ /** Modules the manifest names that this build has no entry for: what a diagnostics page shows by name. */
2076
+ readonly missing: i0.Signal<string[]>;
2077
+ isInstalled(module: string): boolean;
2078
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleActivation, never>;
2079
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
2080
+ }
2081
+ /**
2082
+ * `canMatch` for a module's mount: the route exists only where the module is
2083
+ * installed. Answered once the manifest is in: on a cold deep link the router
2084
+ * recognises the URL while the initializer is still fetching `/theme/config`
2085
+ * (the two run as parallel initializers), and a synchronous read then found
2086
+ * no module installed, so every pasted module URL fell to the wildcard and
2087
+ * landed on the dashboard (measured 2026-09-22: no chunk requested for the
2088
+ * mount, the dashboard's instead). `ready$` is what the auth guard waits on
2089
+ * too, and it fires whether or not the config fetch succeeded.
2090
+ *
2091
+ * !! Then the console's own manifest, when someone is signed in: `ui.modules` is
2092
+ * the console's, not the public config's, and arrives with it
2093
+ * ({@link ConsoleAccessService}). canMatch runs before the layout's auth guard,
2094
+ * which asks the same question, so answering from the public config alone said
2095
+ * "no" to every module once that config stopped carrying them. Nobody signed in:
2096
+ * nothing is asked, the answer is the public config's, and the layout's guard
2097
+ * sends the person to sign in. A refusal merges nothing, so the answer is no.
2098
+ */
2099
+ declare function consoleModuleInstalled(module: string): CanMatchFn;
2100
+ /**
2101
+ * The children a theme mounts under its authenticated layout for the
2102
+ * assembled entries: one `Route` per mount, lazy as written, `data` from the
2103
+ * entry's nav hint, `canMatch` from the manifest. The theme keeps its own
2104
+ * shape around them (login, the layout, `canActivate: [authGuard]`, the
2105
+ * wildcard).
2106
+ */
2107
+ declare function consoleChildren(entries: readonly ConsoleEntry[]): Routes;
2108
+ /**
2109
+ * The providers a theme adds for the assembled entries: the store with the
2110
+ * host's states and every module state, the registry bindings (an
2111
+ * initializer, so they exist before the first render), every module
2112
+ * provision, and {@link CONSOLE_ENTRIES} for the shell. Place it AFTER the
2113
+ * library providers a module's provisions extend (the editor bridge, the
2114
+ * PDF viewer), as the hand-written lines were.
2115
+ */
2116
+ declare function provideConsole(entries: readonly ConsoleEntry[], options: {
2117
+ readonly hostStates: readonly Type<unknown>[];
2118
+ }): EnvironmentProviders;
2119
+
1358
2120
  /**
1359
2121
  * Phase H7 -- admin Site Selector.
1360
2122
  *
@@ -1411,5 +2173,5 @@ interface CurrentSectionPort {
1411
2173
  */
1412
2174
  declare const CURRENT_SECTION: InjectionToken<CurrentSectionPort>;
1413
2175
 
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 };
2176
+ export { AppConfigState, AppInitService, AuthRefreshCoordinator, AuthState, BACKGROUND_REQUEST, BYPASS_AUTH, BYPASS_ELEVATION, CONSOLE_CONTRACT, CONSOLE_ENTRIES, CURRENT_SECTION, CmsLoaderComponent, ComponentRegistry, ConfigService, ConsoleAccessService, ConsoleActivation, ConsoleAssemblyError, ConsolePanelHost, CrossTabAuthSyncService, ELEVATION_NOTICE, ELEVATION_PROMPT, ELEVATION_REQUIRED_HEADER, ELEVATION_WARNING_FACTOR_MISSING, ELEVATION_WARNING_MFA_OFF, ELEVATION_WARNING_PASSWORD_NOT_SET, ElevationService, EntitySearchService, ErrorHandlerService, IdentityApiClient, Login, LoginComponent, Logout, NaviGraphService, PatchCurrentUser, RealtimeTokenClient, RestoreSession, SetAppConfig, SetTokens, SidebarStateService, ThemeService, UserPreferencesService, assembleConsole, authGuard, authInterceptor, consoleChildren, consoleEntry, consoleModuleInstalled, elevationInterceptor, installedConsoleModules, isElevationRefusal, isPortProvision, loginPageGuard, provideConsole, resolvePattern, satisfiesRange, sectionInterceptor };
2177
+ export type { ApiManifest, AppConfigStateModel, AuthApiManifest, AuthStateModel, CentrifugoConnectionTokenDto, CentrifugoSubscriptionTokenDto, ConsoleAccess, ConsoleBinding, ConsoleEntry, ConsoleManifestResponse, ConsoleNavHint, ConsoleOverlay, ConsolePanel, ConsolePortProvision, ConsoleProvision, ConsoleRouteMount, ConsoleTopbarItem, ContentApiManifest, CurrentSectionPort, DataGridApiManifest, DataGridPreference, DataGridPreferences, DataSourceDefinition, DataSourceLoading, DataSourceOption, DataSourceType, DataSourceWidget, DatagridConfig, DialogConfig, DocumentApiManifest, DomainExplorerApiManifest, DynamicEntityApiManifest, EditorApiManifest, EditorApiManifestEntry, EditorApiManifestProfile, ElevationChange, ElevationChangeKind, ElevationDropReason, ElevationEnded, ElevationEndedReason, ElevationNoticePort, ElevationPromptPort, ElevationPromptRequest, ElevationState, ElevationWarning, 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, UiApiManifest, UiModuleEntryManifest, UserDto, ValidatorDefinition, VfsApiManifest, ViewerApiManifest, ViewerDefinitionApiManifest, ViewerProfileApiManifest, VisibilityCondition, VisibilityOperator };