@memberjunction/ng-explorer-core 6.2.0-edge.0 → 6.2.0-edge.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/app-routing.module.d.ts +23 -1
  2. package/dist/app-routing.module.d.ts.map +1 -1
  3. package/dist/app-routing.module.js +50 -14
  4. package/dist/app-routing.module.js.map +1 -1
  5. package/dist/lib/command-palette/command-palette.component.d.ts +7 -1
  6. package/dist/lib/command-palette/command-palette.component.d.ts.map +1 -1
  7. package/dist/lib/command-palette/command-palette.component.js +28 -14
  8. package/dist/lib/command-palette/command-palette.component.js.map +1 -1
  9. package/dist/lib/conversation-feedback/conversation-feedback.d.ts +103 -13
  10. package/dist/lib/conversation-feedback/conversation-feedback.d.ts.map +1 -1
  11. package/dist/lib/conversation-feedback/conversation-feedback.js +219 -67
  12. package/dist/lib/conversation-feedback/conversation-feedback.js.map +1 -1
  13. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.d.ts +64 -10
  14. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.d.ts.map +1 -1
  15. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +172 -66
  16. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
  17. package/dist/lib/generic/resource-container-component.d.ts +19 -5
  18. package/dist/lib/generic/resource-container-component.d.ts.map +1 -1
  19. package/dist/lib/generic/resource-container-component.js +43 -13
  20. package/dist/lib/generic/resource-container-component.js.map +1 -1
  21. package/dist/lib/guards/entities.guard.d.ts +4 -0
  22. package/dist/lib/guards/entities.guard.d.ts.map +1 -1
  23. package/dist/lib/guards/entities.guard.js +10 -2
  24. package/dist/lib/guards/entities.guard.js.map +1 -1
  25. package/dist/lib/identity-claims/claim-redeem.component.d.ts +4 -0
  26. package/dist/lib/identity-claims/claim-redeem.component.d.ts.map +1 -1
  27. package/dist/lib/identity-claims/claim-redeem.component.js +10 -2
  28. package/dist/lib/identity-claims/claim-redeem.component.js.map +1 -1
  29. package/dist/lib/oauth/oauth-callback.component.d.ts +4 -0
  30. package/dist/lib/oauth/oauth-callback.component.d.ts.map +1 -1
  31. package/dist/lib/oauth/oauth-callback.component.js +10 -2
  32. package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
  33. package/dist/lib/profile/profile-dialog.component.d.ts.map +1 -1
  34. package/dist/lib/profile/profile-dialog.component.js +194 -191
  35. package/dist/lib/profile/profile-dialog.component.js.map +1 -1
  36. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +20 -2
  37. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
  38. package/dist/lib/resource-wrappers/artifact-resource.component.js +33 -9
  39. package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
  40. package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +80 -10
  41. package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
  42. package/dist/lib/resource-wrappers/chat-collections-resource.component.js +168 -56
  43. package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
  44. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +186 -23
  45. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
  46. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +500 -177
  47. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
  48. package/dist/lib/resource-wrappers/chat-deeplink-params.d.ts +2 -0
  49. package/dist/lib/resource-wrappers/chat-deeplink-params.d.ts.map +1 -1
  50. package/dist/lib/resource-wrappers/chat-deeplink-params.js +5 -1
  51. package/dist/lib/resource-wrappers/chat-deeplink-params.js.map +1 -1
  52. package/dist/lib/resource-wrappers/chat-search-routing.d.ts +25 -0
  53. package/dist/lib/resource-wrappers/chat-search-routing.d.ts.map +1 -0
  54. package/dist/lib/resource-wrappers/chat-search-routing.js +24 -0
  55. package/dist/lib/resource-wrappers/chat-search-routing.js.map +1 -0
  56. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +14 -2
  57. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
  58. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +32 -8
  59. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
  60. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +54 -8
  61. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
  62. package/dist/lib/resource-wrappers/dashboard-resource.component.js +138 -46
  63. package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
  64. package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts +223 -30
  65. package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts.map +1 -1
  66. package/dist/lib/resource-wrappers/livekit-room-resource.component.js +438 -118
  67. package/dist/lib/resource-wrappers/livekit-room-resource.component.js.map +1 -1
  68. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts +7 -3
  69. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
  70. package/dist/lib/resource-wrappers/search-results-resource.component.js +23 -15
  71. package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
  72. package/dist/lib/resource-wrappers/view-resource.component.d.ts +68 -10
  73. package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
  74. package/dist/lib/resource-wrappers/view-resource.component.js +178 -62
  75. package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
  76. package/dist/lib/services/startup-validation.service.d.ts +4 -0
  77. package/dist/lib/services/startup-validation.service.d.ts.map +1 -1
  78. package/dist/lib/services/startup-validation.service.js +10 -2
  79. package/dist/lib/services/startup-validation.service.js.map +1 -1
  80. package/dist/lib/services/system-validation.service.d.ts +13 -1
  81. package/dist/lib/services/system-validation.service.d.ts.map +1 -1
  82. package/dist/lib/services/system-validation.service.js +29 -5
  83. package/dist/lib/services/system-validation.service.js.map +1 -1
  84. package/dist/lib/shell/components/dialogs/app-access-dialog.component.d.ts +54 -4
  85. package/dist/lib/shell/components/dialogs/app-access-dialog.component.d.ts.map +1 -1
  86. package/dist/lib/shell/components/dialogs/app-access-dialog.component.js +130 -40
  87. package/dist/lib/shell/components/dialogs/app-access-dialog.component.js.map +1 -1
  88. package/dist/lib/shell/components/header/app-nav.component.d.ts +29 -1
  89. package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
  90. package/dist/lib/shell/components/header/app-nav.component.js +56 -12
  91. package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
  92. package/dist/lib/shell/components/header/app-switcher.component.d.ts +33 -5
  93. package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -1
  94. package/dist/lib/shell/components/header/app-switcher.component.js +85 -29
  95. package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
  96. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +1 -1
  97. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -1
  98. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +50 -6
  99. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
  100. package/dist/lib/shell/components/tabs/component-cache-manager.js +95 -22
  101. package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
  102. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +91 -9
  103. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  104. package/dist/lib/shell/components/tabs/tab-container.component.js +257 -101
  105. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  106. package/dist/lib/shell/loading-themes.d.ts +16 -12
  107. package/dist/lib/shell/loading-themes.d.ts.map +1 -1
  108. package/dist/lib/shell/loading-themes.js +145 -137
  109. package/dist/lib/shell/loading-themes.js.map +1 -1
  110. package/dist/lib/shell/readiness-beacon.d.ts +2 -0
  111. package/dist/lib/shell/readiness-beacon.d.ts.map +1 -1
  112. package/dist/lib/shell/readiness-beacon.js +5 -1
  113. package/dist/lib/shell/readiness-beacon.js.map +1 -1
  114. package/dist/lib/shell/services/about-dialog.service.d.ts +6 -0
  115. package/dist/lib/shell/services/about-dialog.service.d.ts.map +1 -1
  116. package/dist/lib/shell/services/about-dialog.service.js +16 -4
  117. package/dist/lib/shell/services/about-dialog.service.js.map +1 -1
  118. package/dist/lib/shell/services/profile-dialog.service.d.ts +6 -0
  119. package/dist/lib/shell/services/profile-dialog.service.d.ts.map +1 -1
  120. package/dist/lib/shell/services/profile-dialog.service.js +16 -4
  121. package/dist/lib/shell/services/profile-dialog.service.js.map +1 -1
  122. package/dist/lib/shell/services/user-sharing-center-dialog.service.d.ts +6 -0
  123. package/dist/lib/shell/services/user-sharing-center-dialog.service.d.ts.map +1 -1
  124. package/dist/lib/shell/services/user-sharing-center-dialog.service.js +15 -3
  125. package/dist/lib/shell/services/user-sharing-center-dialog.service.js.map +1 -1
  126. package/dist/lib/shell/shell.component.d.ts +262 -39
  127. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  128. package/dist/lib/shell/shell.component.js +688 -292
  129. package/dist/lib/shell/shell.component.js.map +1 -1
  130. package/dist/lib/single-dashboard/Components/add-item/add-item.component.d.ts +64 -8
  131. package/dist/lib/single-dashboard/Components/add-item/add-item.component.d.ts.map +1 -1
  132. package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +129 -37
  133. package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
  134. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.d.ts +26 -2
  135. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.d.ts.map +1 -1
  136. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js +48 -10
  137. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js.map +1 -1
  138. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts +84 -6
  139. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts.map +1 -1
  140. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js +133 -29
  141. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js.map +1 -1
  142. package/dist/lib/single-dashboard/single-dashboard.component.d.ts +173 -23
  143. package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
  144. package/dist/lib/single-dashboard/single-dashboard.component.js +383 -109
  145. package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
  146. package/dist/lib/single-list-detail/single-list-detail.component.d.ts +383 -55
  147. package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
  148. package/dist/lib/single-list-detail/single-list-detail.component.js +962 -326
  149. package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
  150. package/dist/lib/single-query/single-query.component.d.ts +22 -2
  151. package/dist/lib/single-query/single-query.component.d.ts.map +1 -1
  152. package/dist/lib/single-query/single-query.component.js +40 -10
  153. package/dist/lib/single-query/single-query.component.js.map +1 -1
  154. package/dist/lib/single-record/single-record.component.d.ts +41 -3
  155. package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
  156. package/dist/lib/single-record/single-record.component.js +71 -11
  157. package/dist/lib/single-record/single-record.component.js.map +1 -1
  158. package/dist/lib/single-search-result/single-search-result.component.d.ts +23 -3
  159. package/dist/lib/single-search-result/single-search-result.component.d.ts.map +1 -1
  160. package/dist/lib/single-search-result/single-search-result.component.js +42 -12
  161. package/dist/lib/single-search-result/single-search-result.component.js.map +1 -1
  162. package/dist/lib/system-validation/system-validation-banner.component.d.ts +12 -2
  163. package/dist/lib/system-validation/system-validation-banner.component.d.ts.map +1 -1
  164. package/dist/lib/system-validation/system-validation-banner.component.js +25 -5
  165. package/dist/lib/system-validation/system-validation-banner.component.js.map +1 -1
  166. package/dist/lib/user-menu/user-menu.types.d.ts +4 -0
  167. package/dist/lib/user-menu/user-menu.types.d.ts.map +1 -1
  168. package/dist/lib/user-menu/user-menu.types.js +10 -2
  169. package/dist/lib/user-menu/user-menu.types.js.map +1 -1
  170. package/dist/lib/user-notifications/user-notifications.component.d.ts +59 -7
  171. package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
  172. package/dist/lib/user-notifications/user-notifications.component.js +151 -47
  173. package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
  174. package/dist/lib/user-profile/user-profile.component.d.ts +3 -2
  175. package/dist/lib/user-profile/user-profile.component.d.ts.map +1 -1
  176. package/dist/lib/user-profile/user-profile.component.js +5 -4
  177. package/dist/lib/user-profile/user-profile.component.js.map +1 -1
  178. package/package.json +52 -52
@@ -15,7 +15,7 @@ import { InstanceConfigEngine, UserInfoEngine } from '@memberjunction/core-entit
15
15
  import { PACKAGE_VERSION } from '@memberjunction/graphql-dataprovider';
16
16
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
17
17
  import { ApplyShellChromePolicy, BaseShellChromePolicy } from './shell-chrome-policy';
18
- import { setReadinessBeacon } from './readiness-beacon';
18
+ import { SetReadinessBeacon } from './readiness-beacon';
19
19
  import * as i0 from "@angular/core";
20
20
  import * as i1 from "@memberjunction/ng-base-application";
21
21
  import * as i2 from "@memberjunction/ng-shared";
@@ -568,7 +568,7 @@ export class ShellComponent extends BaseAngularComponent {
568
568
  profileDialogService;
569
569
  viewContainerRef;
570
570
  titleService;
571
- developerModeService;
571
+ DeveloperModeService;
572
572
  startupValidationService;
573
573
  commandPaletteService;
574
574
  themeService;
@@ -582,7 +582,15 @@ export class ShellComponent extends BaseAngularComponent {
582
582
  /** Newest workspace configuration emission — see the staleness guard in the Configuration subscription */
583
583
  latestSyncedConfig = null;
584
584
  initialNavigationComplete = false; // Track if initial navigation has completed
585
- activeApp = null;
585
+ ActiveApp = null;
586
+ /** @deprecated Use {@link ActiveApp}. */
587
+ get activeApp() {
588
+ return this.ActiveApp;
589
+ }
590
+ /** @deprecated Use {@link ActiveApp}. */
591
+ set activeApp(value) {
592
+ this.ActiveApp = value;
593
+ }
586
594
  _loading = true;
587
595
  /**
588
596
  * Shell loading-screen flag, and the source of the Computer Use readiness
@@ -594,16 +602,40 @@ export class ShellComponent extends BaseAngularComponent {
594
602
  * places assign `loading` — routing, resource load, app switch, error paths.
595
603
  * Publishing here means every one of them stays correct for free.
596
604
  */
597
- get loading() {
605
+ get Loading() {
598
606
  return this._loading;
599
607
  }
600
- set loading(value) {
608
+ set Loading(value) {
601
609
  this._loading = value;
602
- setReadinessBeacon(!value);
610
+ SetReadinessBeacon(!value);
611
+ }
612
+ /** @deprecated Use {@link Loading}. */
613
+ get loading() {
614
+ return this.Loading;
615
+ }
616
+ /** @deprecated Use {@link Loading}. */
617
+ set loading(value) {
618
+ this.Loading = value;
619
+ }
620
+ Initialized = false;
621
+ /** @deprecated Use {@link Initialized}. */
622
+ get initialized() {
623
+ return this.Initialized;
624
+ }
625
+ /** @deprecated Use {@link Initialized}. */
626
+ set initialized(value) {
627
+ this.Initialized = value;
603
628
  }
604
- initialized = false;
605
629
  waitingForFirstResource = false;
606
- tabBarVisible = true; // Controlled by workspace manager
630
+ TabBarVisible = true;
631
+ /** @deprecated Use {@link TabBarVisible}. */
632
+ get tabBarVisible() {
633
+ return this.TabBarVisible;
634
+ }
635
+ /** @deprecated Use {@link TabBarVisible}. */
636
+ set tabBarVisible(value) {
637
+ this.TabBarVisible = value;
638
+ } // Controlled by workspace manager
607
639
  /**
608
640
  * True when the deployment uses the records-as-tabs record-open style
609
641
  * (`Shell.RecordOpen.Style` != 'classic'): records open as native Golden
@@ -614,15 +646,79 @@ export class ShellComponent extends BaseAngularComponent {
614
646
  get RecordTabsStyle() {
615
647
  return this.chromeFlags.recordOpenStyle === 'records';
616
648
  }
617
- userMenuVisible = false; // User avatar context menu
618
- mobileNavOpen = false; // Mobile navigation drawer
619
- unreadNotificationCount = 0; // Notification badge count
649
+ UserMenuVisible = false;
650
+ /** @deprecated Use {@link UserMenuVisible}. */
651
+ get userMenuVisible() {
652
+ return this.UserMenuVisible;
653
+ }
654
+ /** @deprecated Use {@link UserMenuVisible}. */
655
+ set userMenuVisible(value) {
656
+ this.UserMenuVisible = value;
657
+ } // User avatar context menu
658
+ MobileNavOpen = false;
659
+ /** @deprecated Use {@link MobileNavOpen}. */
660
+ get mobileNavOpen() {
661
+ return this.MobileNavOpen;
662
+ }
663
+ /** @deprecated Use {@link MobileNavOpen}. */
664
+ set mobileNavOpen(value) {
665
+ this.MobileNavOpen = value;
666
+ } // Mobile navigation drawer
667
+ UnreadNotificationCount = 0;
668
+ /** @deprecated Use {@link UnreadNotificationCount}. */
669
+ get unreadNotificationCount() {
670
+ return this.UnreadNotificationCount;
671
+ }
672
+ /** @deprecated Use {@link UnreadNotificationCount}. */
673
+ set unreadNotificationCount(value) {
674
+ this.UnreadNotificationCount = value;
675
+ } // Notification badge count
620
676
  // Global Activity indicator (P3)
621
- activityItems = [];
622
- activityRunningCount = 0;
623
- activityOpen = false;
624
- isViewingSystemTab = false; // True when viewing a resource tab (not associated with a registered app)
625
- loadingAppId = null; // ID of app currently being loaded (for app switcher loading indicator)
677
+ ActivityItems = [];
678
+ /** @deprecated Use {@link ActivityItems}. */
679
+ get activityItems() {
680
+ return this.ActivityItems;
681
+ }
682
+ /** @deprecated Use {@link ActivityItems}. */
683
+ set activityItems(value) {
684
+ this.ActivityItems = value;
685
+ }
686
+ ActivityRunningCount = 0;
687
+ /** @deprecated Use {@link ActivityRunningCount}. */
688
+ get activityRunningCount() {
689
+ return this.ActivityRunningCount;
690
+ }
691
+ /** @deprecated Use {@link ActivityRunningCount}. */
692
+ set activityRunningCount(value) {
693
+ this.ActivityRunningCount = value;
694
+ }
695
+ ActivityOpen = false;
696
+ /** @deprecated Use {@link ActivityOpen}. */
697
+ get activityOpen() {
698
+ return this.ActivityOpen;
699
+ }
700
+ /** @deprecated Use {@link ActivityOpen}. */
701
+ set activityOpen(value) {
702
+ this.ActivityOpen = value;
703
+ }
704
+ IsViewingSystemTab = false;
705
+ /** @deprecated Use {@link IsViewingSystemTab}. */
706
+ get isViewingSystemTab() {
707
+ return this.IsViewingSystemTab;
708
+ }
709
+ /** @deprecated Use {@link IsViewingSystemTab}. */
710
+ set isViewingSystemTab(value) {
711
+ this.IsViewingSystemTab = value;
712
+ } // True when viewing a resource tab (not associated with a registered app)
713
+ LoadingAppId = null;
714
+ /** @deprecated Use {@link LoadingAppId}. */
715
+ get loadingAppId() {
716
+ return this.LoadingAppId;
717
+ }
718
+ /** @deprecated Use {@link LoadingAppId}. */
719
+ set loadingAppId(value) {
720
+ this.LoadingAppId = value;
721
+ } // ID of app currently being loaded (for app switcher loading indicator)
626
722
  // Loading animation state
627
723
  loadingMessageInterval = null;
628
724
  loadingMessageIndex = 0;
@@ -644,32 +740,152 @@ export class ShellComponent extends BaseAngularComponent {
644
740
  MJVersion = PACKAGE_VERSION;
645
741
  loadingResetTimeout = null;
646
742
  loadingResetDelayMs = 20_000; // 20 seconds before showing reset option
647
- currentLoadingText;
648
- currentLoadingColor;
649
- currentLoadingTextColor;
650
- currentLoadingGradient;
651
- currentLoadingAnimation = 'pulse';
743
+ CurrentLoadingText;
744
+ /** @deprecated Use {@link CurrentLoadingText}. */
745
+ get currentLoadingText() {
746
+ return this.CurrentLoadingText;
747
+ }
748
+ /** @deprecated Use {@link CurrentLoadingText}. */
749
+ set currentLoadingText(value) {
750
+ this.CurrentLoadingText = value;
751
+ }
752
+ CurrentLoadingColor;
753
+ /** @deprecated Use {@link CurrentLoadingColor}. */
754
+ get currentLoadingColor() {
755
+ return this.CurrentLoadingColor;
756
+ }
757
+ /** @deprecated Use {@link CurrentLoadingColor}. */
758
+ set currentLoadingColor(value) {
759
+ this.CurrentLoadingColor = value;
760
+ }
761
+ CurrentLoadingTextColor;
762
+ /** @deprecated Use {@link CurrentLoadingTextColor}. */
763
+ get currentLoadingTextColor() {
764
+ return this.CurrentLoadingTextColor;
765
+ }
766
+ /** @deprecated Use {@link CurrentLoadingTextColor}. */
767
+ set currentLoadingTextColor(value) {
768
+ this.CurrentLoadingTextColor = value;
769
+ }
770
+ CurrentLoadingGradient;
771
+ /** @deprecated Use {@link CurrentLoadingGradient}. */
772
+ get currentLoadingGradient() {
773
+ return this.CurrentLoadingGradient;
774
+ }
775
+ /** @deprecated Use {@link CurrentLoadingGradient}. */
776
+ set currentLoadingGradient(value) {
777
+ this.CurrentLoadingGradient = value;
778
+ }
779
+ CurrentLoadingAnimation = 'pulse';
780
+ /** @deprecated Use {@link CurrentLoadingAnimation}. */
781
+ get currentLoadingAnimation() {
782
+ return this.CurrentLoadingAnimation;
783
+ }
784
+ /** @deprecated Use {@link CurrentLoadingAnimation}. */
785
+ set currentLoadingAnimation(value) {
786
+ this.CurrentLoadingAnimation = value;
787
+ }
652
788
  // User avatar state
653
- userImageURL = '';
654
- userIconClass = null;
789
+ UserImageURL = '';
790
+ /** @deprecated Use {@link UserImageURL}. */
791
+ get userImageURL() {
792
+ return this.UserImageURL;
793
+ }
794
+ /** @deprecated Use {@link UserImageURL}. */
795
+ set userImageURL(value) {
796
+ this.UserImageURL = value;
797
+ }
798
+ UserIconClass = null;
799
+ /** @deprecated Use {@link UserIconClass}. */
800
+ get userIconClass() {
801
+ return this.UserIconClass;
802
+ }
803
+ /** @deprecated Use {@link UserIconClass}. */
804
+ set userIconClass(value) {
805
+ this.UserIconClass = value;
806
+ }
655
807
  userName = '';
656
- userEmail = '';
808
+ UserEmail = '';
809
+ /** @deprecated Use {@link UserEmail}. */
810
+ get userEmail() {
811
+ return this.UserEmail;
812
+ }
813
+ /** @deprecated Use {@link UserEmail}. */
814
+ set userEmail(value) {
815
+ this.UserEmail = value;
816
+ }
657
817
  userEntity = null;
658
818
  // User menu plugin system
659
819
  userMenu = null;
660
- userMenuElements = [];
820
+ UserMenuElements = [];
821
+ /** @deprecated Use {@link UserMenuElements}. */
822
+ get userMenuElements() {
823
+ return this.UserMenuElements;
824
+ }
825
+ /** @deprecated Use {@link UserMenuElements}. */
826
+ set userMenuElements(value) {
827
+ this.UserMenuElements = value;
828
+ }
661
829
  destroy$ = new Subject();
662
830
  // Pin progress overlay
663
831
  PinProgressVisible = false;
664
832
  PinProgressText = '';
665
833
  // Search state
666
- isSearchOpen = false;
667
- searchableEntities = [];
668
- selectedEntity = null;
669
- searchInput;
834
+ IsSearchOpen = false;
835
+ /** @deprecated Use {@link IsSearchOpen}. */
836
+ get isSearchOpen() {
837
+ return this.IsSearchOpen;
838
+ }
839
+ /** @deprecated Use {@link IsSearchOpen}. */
840
+ set isSearchOpen(value) {
841
+ this.IsSearchOpen = value;
842
+ }
843
+ SearchableEntities = [];
844
+ /** @deprecated Use {@link SearchableEntities}. */
845
+ get searchableEntities() {
846
+ return this.SearchableEntities;
847
+ }
848
+ /** @deprecated Use {@link SearchableEntities}. */
849
+ set searchableEntities(value) {
850
+ this.SearchableEntities = value;
851
+ }
852
+ SelectedEntity = null;
853
+ /** @deprecated Use {@link SelectedEntity}. */
854
+ get selectedEntity() {
855
+ return this.SelectedEntity;
856
+ }
857
+ /** @deprecated Use {@link SelectedEntity}. */
858
+ set selectedEntity(value) {
859
+ this.SelectedEntity = value;
860
+ }
861
+ SearchInput;
862
+ /** @deprecated Use {@link SearchInput}. */
863
+ get searchInput() {
864
+ return this.SearchInput;
865
+ }
866
+ /** @deprecated Use {@link SearchInput}. */
867
+ set searchInput(value) {
868
+ this.SearchInput = value;
869
+ }
670
870
  /** Mobile drawer + its toggle — used to return focus to the toggle when the drawer closes. */
671
- mobileNavDrawer;
672
- mobileNavToggle;
871
+ MobileNavDrawer;
872
+ /** @deprecated Use {@link MobileNavDrawer}. */
873
+ get mobileNavDrawer() {
874
+ return this.MobileNavDrawer;
875
+ }
876
+ /** @deprecated Use {@link MobileNavDrawer}. */
877
+ set mobileNavDrawer(value) {
878
+ this.MobileNavDrawer = value;
879
+ }
880
+ MobileNavToggle;
881
+ /** @deprecated Use {@link MobileNavToggle}. */
882
+ get mobileNavToggle() {
883
+ return this.MobileNavToggle;
884
+ }
885
+ /** @deprecated Use {@link MobileNavToggle}. */
886
+ set mobileNavToggle(value) {
887
+ this.MobileNavToggle = value;
888
+ }
673
889
  // Legacy universal search overlay (omnibar-off MOBILE path) — opened by the
674
890
  // mobile search icon or Ctrl/Cmd+K when the inline composite isn't visible.
675
891
  // Desktop omnibar-off uses the inline header composite instead.
@@ -765,7 +981,7 @@ export class ShellComponent extends BaseAngularComponent {
765
981
  // is the same guarantee read off state we already have, instead of a new
766
982
  // dirty-tracking pipeline.)
767
983
  this.workspaceManager.RecordsRegionTabFilter = this.resolvedRecordOpenStyle === 'records'
768
- ? (tab) => IsRecordsRegionTab(tab.configuration) && !this.tabContainerRef?.IsRecordTabEditing(tab.id)
984
+ ? (tab) => IsRecordsRegionTab(tab.configuration) && !this.TabContainerRef?.IsRecordTabEditing(tab.id)
769
985
  : null;
770
986
  }
771
987
  get ShowSearchBar() {
@@ -851,25 +1067,57 @@ export class ShellComponent extends BaseAngularComponent {
851
1067
  /** Palette footer gear → My Profile (where the Command Palette section lives). */
852
1068
  OnPaletteSettingsRequested() {
853
1069
  this.profileDialogService.open(this.viewContainerRef, {
854
- avatarUrl: this.userImageURL || null,
855
- avatarIconClass: this.userIconClass || null
1070
+ avatarUrl: this.UserImageURL || null,
1071
+ avatarIconClass: this.UserIconClass || null
856
1072
  });
857
1073
  }
858
- omnibarPalette;
1074
+ OmnibarPalette;
1075
+ /** @deprecated Use {@link OmnibarPalette}. */
1076
+ get omnibarPalette() {
1077
+ return this.OmnibarPalette;
1078
+ }
1079
+ /** @deprecated Use {@link OmnibarPalette}. */
1080
+ set omnibarPalette(value) {
1081
+ this.OmnibarPalette = value;
1082
+ }
859
1083
  /** Legacy inline search composite (omnibar-off desktop). Structural typing keeps
860
1084
  the shell decoupled from the ng-search component class. */
861
- shellSearchComposite;
1085
+ ShellSearchComposite;
1086
+ /** @deprecated Use {@link ShellSearchComposite}. */
1087
+ get shellSearchComposite() {
1088
+ return this.ShellSearchComposite;
1089
+ }
1090
+ /** @deprecated Use {@link ShellSearchComposite}. */
1091
+ set shellSearchComposite(value) {
1092
+ this.ShellSearchComposite = value;
1093
+ }
862
1094
  /** Header affordance click → open the palette. */
863
1095
  OpenOmnibar(initialQuery = '') {
864
1096
  if (!this.ShowSearchBar) {
865
1097
  return;
866
1098
  }
867
- this.omnibarPalette?.Open(initialQuery);
1099
+ this.OmnibarPalette?.Open(initialQuery);
868
1100
  }
869
1101
  // Tab container reference for thumbnail capture
870
- tabContainerRef;
1102
+ TabContainerRef;
1103
+ /** @deprecated Use {@link TabContainerRef}. */
1104
+ get tabContainerRef() {
1105
+ return this.TabContainerRef;
1106
+ }
1107
+ /** @deprecated Use {@link TabContainerRef}. */
1108
+ set tabContainerRef(value) {
1109
+ this.TabContainerRef = value;
1110
+ }
871
1111
  // App access dialog
872
- appAccessDialog;
1112
+ AppAccessDialog;
1113
+ /** @deprecated Use {@link AppAccessDialog}. */
1114
+ get appAccessDialog() {
1115
+ return this.AppAccessDialog;
1116
+ }
1117
+ /** @deprecated Use {@link AppAccessDialog}. */
1118
+ set appAccessDialog(value) {
1119
+ this.AppAccessDialog = value;
1120
+ }
873
1121
  pendingAppPath = null; // Store the app path we tried to access
874
1122
  /**
875
1123
  * Nav Bar apps positioned to the left of the app switcher.
@@ -878,26 +1126,42 @@ export class ShellComponent extends BaseAngularComponent {
878
1126
  * this is bound directly in the shell nav template's @for/@if. Filters out apps
879
1127
  * that have HideNavBarIconWhenActive=true and are currently active.
880
1128
  */
881
- leftOfSwitcherApps = [];
1129
+ LeftOfSwitcherApps = [];
1130
+ /** @deprecated Use {@link LeftOfSwitcherApps}. */
1131
+ get leftOfSwitcherApps() {
1132
+ return this.LeftOfSwitcherApps;
1133
+ }
1134
+ /** @deprecated Use {@link LeftOfSwitcherApps}. */
1135
+ set leftOfSwitcherApps(value) {
1136
+ this.LeftOfSwitcherApps = value;
1137
+ }
882
1138
  /**
883
1139
  * Nav Bar apps positioned to the left of the user menu.
884
1140
  * Backing field recomputed only when the active app or nav-app list changes
885
1141
  * (see {@link recomputeNavBarApps}). Filters out apps that have
886
1142
  * HideNavBarIconWhenActive=true and are currently active.
887
1143
  */
888
- leftOfUserMenuApps = [];
1144
+ LeftOfUserMenuApps = [];
1145
+ /** @deprecated Use {@link LeftOfUserMenuApps}. */
1146
+ get leftOfUserMenuApps() {
1147
+ return this.LeftOfUserMenuApps;
1148
+ }
1149
+ /** @deprecated Use {@link LeftOfUserMenuApps}. */
1150
+ set leftOfUserMenuApps(value) {
1151
+ this.LeftOfUserMenuApps = value;
1152
+ }
889
1153
  /**
890
1154
  * Recompute the precomputed nav-bar app arrays. Called only when the active app
891
1155
  * changes or the underlying nav-app list (re)loads — keeping the filtered arrays
892
1156
  * (and their per-app UUIDsEqual checks) out of the per-CD-cycle hot path.
893
1157
  */
894
1158
  recomputeNavBarApps() {
895
- this.leftOfSwitcherApps = this.appManager.GetNavBarApps('Left of App Switcher')
896
- .filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.activeApp?.ID)));
897
- this.leftOfUserMenuApps = this.appManager.GetNavBarApps('Left of User Menu')
898
- .filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.activeApp?.ID)));
1159
+ this.LeftOfSwitcherApps = this.appManager.GetNavBarApps('Left of App Switcher')
1160
+ .filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.ActiveApp?.ID)));
1161
+ this.LeftOfUserMenuApps = this.appManager.GetNavBarApps('Left of User Menu')
1162
+ .filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.ActiveApp?.ID)));
899
1163
  }
900
- constructor(appManager, workspaceManager, layoutManager, tabService, navigationService, route, router, authBase, cdr, userAvatarService, userSharingCenterDialogService, aboutDialogService, profileDialogService, viewContainerRef, titleService, developerModeService, startupValidationService, commandPaletteService, themeService, homePinService, fileOpenService, feedbackDialogService, feedbackService, activityService) {
1164
+ constructor(appManager, workspaceManager, layoutManager, tabService, navigationService, route, router, authBase, cdr, userAvatarService, userSharingCenterDialogService, aboutDialogService, profileDialogService, viewContainerRef, titleService, DeveloperModeService, startupValidationService, commandPaletteService, themeService, homePinService, fileOpenService, feedbackDialogService, feedbackService, activityService) {
901
1165
  super();
902
1166
  this.appManager = appManager;
903
1167
  this.workspaceManager = workspaceManager;
@@ -914,7 +1178,7 @@ export class ShellComponent extends BaseAngularComponent {
914
1178
  this.profileDialogService = profileDialogService;
915
1179
  this.viewContainerRef = viewContainerRef;
916
1180
  this.titleService = titleService;
917
- this.developerModeService = developerModeService;
1181
+ this.DeveloperModeService = DeveloperModeService;
918
1182
  this.startupValidationService = startupValidationService;
919
1183
  this.commandPaletteService = commandPaletteService;
920
1184
  this.themeService = themeService;
@@ -930,7 +1194,7 @@ export class ShellComponent extends BaseAngularComponent {
930
1194
  const providerForServices = this.ProviderToUse;
931
1195
  this.appManager.Provider = providerForServices;
932
1196
  this.workspaceManager.Provider = providerForServices;
933
- this.developerModeService.Provider = providerForServices;
1197
+ this.DeveloperModeService.Provider = providerForServices;
934
1198
  this.startupValidationService.Provider = providerForServices;
935
1199
  if (SharedService.Instance)
936
1200
  SharedService.Instance.Provider = providerForServices;
@@ -939,49 +1203,57 @@ export class ShellComponent extends BaseAngularComponent {
939
1203
  // Initialize animation based on theme configuration
940
1204
  this.initializeAnimationFromTheme();
941
1205
  // Set first message
942
- this.currentLoadingText = this.activeTheme.messages[0];
943
- if (this.activeTheme.staticColors) {
1206
+ this.CurrentLoadingText = this.activeTheme.Messages[0];
1207
+ if (this.activeTheme.StaticColors) {
944
1208
  // Standard theme: keep MJ blue, no gradient
945
- this.currentLoadingColor = this.activeTheme.colors[0];
946
- this.currentLoadingTextColor = '#757575'; // Default gray text
947
- this.currentLoadingGradient = null;
1209
+ this.CurrentLoadingColor = this.activeTheme.Colors[0];
1210
+ this.CurrentLoadingTextColor = '#757575'; // Default gray text
1211
+ this.CurrentLoadingGradient = null;
948
1212
  }
949
1213
  else {
950
1214
  // Themed period: use theme colors and first gradient from the start
951
- this.currentLoadingColor = this.activeTheme.colors[0];
952
- this.currentLoadingTextColor = this.activeTheme.colors[0];
1215
+ this.CurrentLoadingColor = this.activeTheme.Colors[0];
1216
+ this.CurrentLoadingTextColor = this.activeTheme.Colors[0];
953
1217
  // Set initial gradient if theme has gradients
954
- if (this.activeTheme.gradients && this.activeTheme.gradients.length > 0) {
955
- this.currentLoadingGradient = this.activeTheme.gradients[0];
1218
+ if (this.activeTheme.Gradients && this.activeTheme.Gradients.length > 0) {
1219
+ this.CurrentLoadingGradient = this.activeTheme.Gradients[0];
956
1220
  }
957
1221
  else {
958
- this.currentLoadingGradient = null;
1222
+ this.CurrentLoadingGradient = null;
959
1223
  }
960
1224
  }
961
1225
  }
1226
+ /** @deprecated Use {@link DeveloperModeService}. */
1227
+ get developerModeService() {
1228
+ return this.DeveloperModeService;
1229
+ }
1230
+ /** @deprecated Use {@link DeveloperModeService}. */
1231
+ set developerModeService(value) {
1232
+ this.DeveloperModeService = value;
1233
+ }
962
1234
  async ngOnInit() {
963
1235
  try {
964
1236
  MJGlobal.Instance.GetEventListener(true).subscribe(async (loginEvent) => {
965
1237
  if (loginEvent.event === MJEventType.LoggedIn) {
966
1238
  if (this.authBase.initialPath === "/") {
967
1239
  // Base route - no need to wait for NavigationEnd
968
- await this.initializeShell();
1240
+ await this.InitializeShell();
969
1241
  }
970
1242
  else {
971
1243
  // Deep link route - wait for NavigationEnd to ensure router URL is correct
972
1244
  this.router.events.pipe(filter((event) => event instanceof NavigationEnd), filter(() => !this.initialNavigationComplete)).subscribe(async () => {
973
1245
  this.initialNavigationComplete = true;
974
- await this.initializeShell();
1246
+ await this.InitializeShell();
975
1247
  });
976
1248
  }
977
1249
  }
978
1250
  });
979
1251
  }
980
1252
  catch (error) {
981
- this.loading = false;
1253
+ this.Loading = false;
982
1254
  }
983
1255
  }
984
- async initializeShell() {
1256
+ async InitializeShell() {
985
1257
  // Start the loading animation with cycling messages
986
1258
  this.startLoadingAnimation();
987
1259
  // Initialize application manager (subscribes to LoggedIn event)
@@ -1013,26 +1285,26 @@ export class ShellComponent extends BaseAngularComponent {
1013
1285
  await this.workspaceManager.Initialize(user.ID);
1014
1286
  // Subscribe to tab bar visibility changes
1015
1287
  this.subscriptions.push(this.workspaceManager.TabBarVisible.subscribe(visible => {
1016
- this.tabBarVisible = visible;
1288
+ this.TabBarVisible = visible;
1017
1289
  }));
1018
1290
  // The host's chrome policy says its answer may have changed (org or plan switch)
1019
1291
  this.subscriptions.push(this.chromePolicy.Changed.subscribe(() => this.onChromePolicyChanged()));
1020
1292
  // Subscribe to the global Activity tracker (Run Pipeline, Sync, Cluster, …)
1021
1293
  this.subscriptions.push(this.activityService.Activities$.subscribe(items => {
1022
- this.activityItems = items;
1023
- this.activityRunningCount = items.filter(i => i.Status === 'running').length;
1294
+ this.ActivityItems = items;
1295
+ this.ActivityRunningCount = items.filter(i => i.Status === 'running').length;
1024
1296
  if (items.length === 0)
1025
- this.activityOpen = false;
1297
+ this.ActivityOpen = false;
1026
1298
  this.cdr.detectChanges();
1027
1299
  }));
1028
1300
  // Subscribe to unread notification count changes
1029
1301
  this.subscriptions.push(MJNotificationService.UnreadCount$.subscribe(count => {
1030
- this.unreadNotificationCount = count;
1302
+ this.UnreadNotificationCount = count;
1031
1303
  this.cdr.detectChanges();
1032
1304
  }));
1033
1305
  // Subscribe to active app changes
1034
1306
  this.subscriptions.push(this.appManager.ActiveApp.subscribe(async (app) => {
1035
- this.activeApp = app;
1307
+ this.ActiveApp = app;
1036
1308
  this.recomputeNavBarApps();
1037
1309
  this.cdr.detectChanges();
1038
1310
  // Create default tab when app is activated ONLY if:
@@ -1149,7 +1421,7 @@ export class ShellComponent extends BaseAngularComponent {
1149
1421
  }
1150
1422
  // Subscribe to workspace configuration changes to sync URL and active app
1151
1423
  this.subscriptions.push(this.workspaceManager.Configuration.subscribe(async (config) => {
1152
- if (config && this.initialized) {
1424
+ if (config && this.Initialized) {
1153
1425
  this.latestSyncedConfig = config;
1154
1426
  // Sync active app with active tab's application
1155
1427
  await this.syncActiveAppWithTab(config);
@@ -1171,7 +1443,7 @@ export class ShellComponent extends BaseAngularComponent {
1171
1443
  }));
1172
1444
  // Subscribe to router navigation events (for browser back/forward)
1173
1445
  this.subscriptions.push(this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(event => {
1174
- if (this.initialized) {
1446
+ if (this.Initialized) {
1175
1447
  this.syncWorkspaceWithUrl(event.urlAfterRedirects || event.url);
1176
1448
  }
1177
1449
  }));
@@ -1197,16 +1469,16 @@ export class ShellComponent extends BaseAngularComponent {
1197
1469
  this.subscriptions.push(MJGlobal.Instance.GetEventListener(false).subscribe(async (event) => {
1198
1470
  if (event.event === MJEventType.TenantChanged) {
1199
1471
  if (event.eventCode === 'TenantChanging') {
1200
- this.currentLoadingText = 'Switching organization...';
1201
- this.loading = true;
1472
+ this.CurrentLoadingText = 'Switching organization...';
1473
+ this.Loading = true;
1202
1474
  this.cdr.detectChanges();
1203
1475
  }
1204
- else if (event.eventCode === 'TenantChanged' && this.tabContainerRef) {
1476
+ else if (event.eventCode === 'TenantChanged' && this.TabContainerRef) {
1205
1477
  try {
1206
- await this.tabContainerRef.ReloadAllTabs();
1478
+ await this.TabContainerRef.ReloadAllTabs();
1207
1479
  }
1208
1480
  finally {
1209
- this.loading = false;
1481
+ this.Loading = false;
1210
1482
  this.cdr.detectChanges();
1211
1483
  }
1212
1484
  }
@@ -1214,7 +1486,7 @@ export class ShellComponent extends BaseAngularComponent {
1214
1486
  }));
1215
1487
  // Load searchable entities for search functionality
1216
1488
  await this.loadSearchableEntities();
1217
- this.initialized = true;
1489
+ this.Initialized = true;
1218
1490
  this.waitingForFirstResource = true;
1219
1491
  // Decide whether to restore workspace state or honor the current URL.
1220
1492
  // If the user navigated to a specific path (deep link, bookmark, typed URL),
@@ -1239,6 +1511,10 @@ export class ShellComponent extends BaseAngularComponent {
1239
1511
  // NG0100 ExpressionChangedAfterItHasBeenCheckedError in dev mode
1240
1512
  this.cdr.detectChanges();
1241
1513
  }
1514
+ /** @deprecated Use {@link InitializeShell}. */
1515
+ async initializeShell() {
1516
+ return this.InitializeShell();
1517
+ }
1242
1518
  /**
1243
1519
  * Handle deep links from URL
1244
1520
  * Resource URLs like /resource/record/Companies/123 are handled by ResourceResolver
@@ -1274,7 +1550,7 @@ export class ShellComponent extends BaseAngularComponent {
1274
1550
  // 1. We're initialized (past the startup phase)
1275
1551
  // 2. App is different from current
1276
1552
  // 3. Either NOT in URL-based navigation mode, OR this IS a URL-based tab
1277
- const shouldSetActiveApp = this.initialized &&
1553
+ const shouldSetActiveApp = this.Initialized &&
1278
1554
  app &&
1279
1555
  !UUIDsEqual(currentActiveApp?.ID, request.ApplicationId) &&
1280
1556
  (!this.urlBasedNavigation || isUrlBasedTab);
@@ -1315,7 +1591,7 @@ export class ShellComponent extends BaseAngularComponent {
1315
1591
  }
1316
1592
  // Check if this is a system tab (not associated with a registered app)
1317
1593
  if (tabAppId === SYSTEM_APP_ID) {
1318
- this.isViewingSystemTab = true;
1594
+ this.IsViewingSystemTab = true;
1319
1595
  this.cdr.detectChanges();
1320
1596
  // Don't try to set active app - SYSTEM_APP_ID has no registered app
1321
1597
  // Update browser title with just the tab title (no app context)
@@ -1323,7 +1599,7 @@ export class ShellComponent extends BaseAngularComponent {
1323
1599
  return;
1324
1600
  }
1325
1601
  // Not a system tab - clear the flag
1326
- this.isViewingSystemTab = false;
1602
+ this.IsViewingSystemTab = false;
1327
1603
  this.cdr.detectChanges();
1328
1604
  // Records style: records-REGION tabs are a GLOBAL
1329
1605
  // surface — viewing one must never flip the user's app context. Without
@@ -1333,7 +1609,7 @@ export class ShellComponent extends BaseAngularComponent {
1333
1609
  // stays wherever the user is. Records DOCKED to the workspace fall
1334
1610
  // through: they are ordinary main-layout tabs and DO flip app context.
1335
1611
  if (this.RecordTabsStyle && IsRecordsRegionTab(activeTab.configuration)) {
1336
- this.titleService.setContext(this.activeApp?.Name || null, activeTab.title || null);
1612
+ this.titleService.setContext(this.ActiveApp?.Name || null, activeTab.title || null);
1337
1613
  return;
1338
1614
  }
1339
1615
  // Check if active app needs to be updated
@@ -2055,15 +2331,23 @@ export class ShellComponent extends BaseAngularComponent {
2055
2331
  * user stays within their scoped app. Data access is still enforced
2056
2332
  * server-side by the user's role; this is the UI-confinement layer.
2057
2333
  */
2058
- get appSwitchingLocked() {
2334
+ get AppSwitchingLocked() {
2059
2335
  return !!this.authBase.GetSessionScope()?.restrictedToApplicationId;
2060
2336
  }
2061
- onFirstResourceLoadComplete() {
2337
+ /** @deprecated Use {@link AppSwitchingLocked}. */
2338
+ get appSwitchingLocked() {
2339
+ return this.AppSwitchingLocked;
2340
+ }
2341
+ OnFirstResourceLoadComplete() {
2062
2342
  this.waitingForFirstResource = false;
2063
- this.loading = false;
2343
+ this.Loading = false;
2064
2344
  this.stopLoadingAnimation();
2065
2345
  this.cdr.detectChanges();
2066
2346
  }
2347
+ /** @deprecated Use {@link OnFirstResourceLoadComplete}. */
2348
+ onFirstResourceLoadComplete() {
2349
+ return this.OnFirstResourceLoadComplete();
2350
+ }
2067
2351
  /**
2068
2352
  * Start the loading message cycling animation.
2069
2353
  * Messages cycle every 2.5 seconds without repeating until all are used.
@@ -2120,7 +2404,7 @@ export class ShellComponent extends BaseAngularComponent {
2120
2404
  startLoadingResetTimer() {
2121
2405
  this.cancelLoadingResetTimer();
2122
2406
  this.loadingResetTimeout = setTimeout(() => {
2123
- if (this.loading) {
2407
+ if (this.Loading) {
2124
2408
  this.ShowResetOption = true;
2125
2409
  this.cdr.detectChanges();
2126
2410
  }
@@ -2186,24 +2470,24 @@ export class ShellComponent extends BaseAngularComponent {
2186
2470
  */
2187
2471
  initializeLoadingDisplay() {
2188
2472
  // Set first message
2189
- this.currentLoadingText = this.activeTheme.messages[0];
2190
- if (this.activeTheme.staticColors) {
2473
+ this.CurrentLoadingText = this.activeTheme.Messages[0];
2474
+ if (this.activeTheme.StaticColors) {
2191
2475
  // Standard theme: keep MJ blue, no gradient
2192
- this.currentLoadingColor = this.activeTheme.colors[0];
2193
- this.currentLoadingTextColor = '#757575'; // Default gray text
2194
- this.currentLoadingGradient = null;
2476
+ this.CurrentLoadingColor = this.activeTheme.Colors[0];
2477
+ this.CurrentLoadingTextColor = '#757575'; // Default gray text
2478
+ this.CurrentLoadingGradient = null;
2195
2479
  }
2196
2480
  else {
2197
2481
  // Themed period: use theme colors and first gradient from the start
2198
- this.currentLoadingColor = this.activeTheme.colors[0];
2199
- this.currentLoadingTextColor = this.activeTheme.colors[0];
2482
+ this.CurrentLoadingColor = this.activeTheme.Colors[0];
2483
+ this.CurrentLoadingTextColor = this.activeTheme.Colors[0];
2200
2484
  // Set initial gradient if theme has gradients
2201
- if (this.activeTheme.gradients && this.activeTheme.gradients.length > 0) {
2202
- this.currentLoadingGradient = this.activeTheme.gradients[0];
2485
+ if (this.activeTheme.Gradients && this.activeTheme.Gradients.length > 0) {
2486
+ this.CurrentLoadingGradient = this.activeTheme.Gradients[0];
2203
2487
  this.usedGradientIndices = [0];
2204
2488
  }
2205
2489
  else {
2206
- this.currentLoadingGradient = null;
2490
+ this.CurrentLoadingGradient = null;
2207
2491
  }
2208
2492
  }
2209
2493
  }
@@ -2213,7 +2497,7 @@ export class ShellComponent extends BaseAngularComponent {
2213
2497
  * For standard theme, colors remain static.
2214
2498
  */
2215
2499
  cycleToNextMessage() {
2216
- const messages = this.activeTheme.messages;
2500
+ const messages = this.activeTheme.Messages;
2217
2501
  this.messageCycleCount++;
2218
2502
  // If we've used all messages, reset the used list (but exclude current to avoid immediate repeat)
2219
2503
  if (this.usedMessageIndices.length >= messages.length) {
@@ -2228,10 +2512,10 @@ export class ShellComponent extends BaseAngularComponent {
2228
2512
  this.usedMessageIndices.push(randomIndex);
2229
2513
  this.loadingMessageIndex = randomIndex;
2230
2514
  // Update the message
2231
- this.currentLoadingText = this.activeTheme.messages[randomIndex];
2515
+ this.CurrentLoadingText = this.activeTheme.Messages[randomIndex];
2232
2516
  // Check if it's time to change colors (every 2nd message = every 5 seconds)
2233
2517
  // But only for non-static themes
2234
- if (!this.activeTheme.staticColors && this.messageCycleCount % this.colorChangeEveryNMessages === 0) {
2518
+ if (!this.activeTheme.StaticColors && this.messageCycleCount % this.colorChangeEveryNMessages === 0) {
2235
2519
  this.cycleToNextColor();
2236
2520
  }
2237
2521
  }
@@ -2242,8 +2526,8 @@ export class ShellComponent extends BaseAngularComponent {
2242
2526
  */
2243
2527
  cycleToNextColor() {
2244
2528
  // Cycle to next gradient if available
2245
- if (this.activeTheme.gradients && this.activeTheme.gradients.length > 0) {
2246
- const gradients = this.activeTheme.gradients;
2529
+ if (this.activeTheme.Gradients && this.activeTheme.Gradients.length > 0) {
2530
+ const gradients = this.activeTheme.Gradients;
2247
2531
  // If we've used all gradients, reset (but exclude current to avoid immediate repeat)
2248
2532
  if (this.usedGradientIndices.length >= gradients.length) {
2249
2533
  const lastUsed = this.usedGradientIndices[this.usedGradientIndices.length - 1];
@@ -2253,15 +2537,15 @@ export class ShellComponent extends BaseAngularComponent {
2253
2537
  const currentIndex = this.usedGradientIndices[this.usedGradientIndices.length - 1] ?? -1;
2254
2538
  const nextIndex = (currentIndex + 1) % gradients.length;
2255
2539
  this.usedGradientIndices.push(nextIndex);
2256
- this.currentLoadingGradient = gradients[nextIndex];
2540
+ this.CurrentLoadingGradient = gradients[nextIndex];
2257
2541
  }
2258
2542
  // Also cycle text color through theme colors
2259
- const colors = this.activeTheme.colors;
2543
+ const colors = this.activeTheme.Colors;
2260
2544
  if (colors.length > 1) {
2261
2545
  // Get a random color from the theme for text
2262
2546
  const randomIndex = Math.floor(Math.random() * colors.length);
2263
- this.currentLoadingColor = colors[randomIndex];
2264
- this.currentLoadingTextColor = colors[randomIndex];
2547
+ this.CurrentLoadingColor = colors[randomIndex];
2548
+ this.CurrentLoadingTextColor = colors[randomIndex];
2265
2549
  }
2266
2550
  }
2267
2551
  /**
@@ -2270,22 +2554,22 @@ export class ShellComponent extends BaseAngularComponent {
2270
2554
  */
2271
2555
  initializeAnimationFromTheme() {
2272
2556
  this.currentAnimationIndex = 0;
2273
- const themeAnimations = this.activeTheme.animations;
2557
+ const themeAnimations = this.activeTheme.Animations;
2274
2558
  if (!themeAnimations) {
2275
2559
  // No animation config - use random selection for non-standard themes
2276
2560
  if (this.activeTheme.id === 'standard') {
2277
2561
  // Standard theme defaults to pulse only
2278
- this.animationSequence = [{ type: 'pulse' }];
2562
+ this.animationSequence = [{ Type: 'pulse' }];
2279
2563
  }
2280
2564
  else {
2281
2565
  // Random selection for themed holidays without explicit config
2282
2566
  const randomType = this.allAnimationTypes[Math.floor(Math.random() * this.allAnimationTypes.length)];
2283
- this.animationSequence = [{ type: randomType }];
2567
+ this.animationSequence = [{ Type: randomType }];
2284
2568
  }
2285
2569
  }
2286
2570
  else if (typeof themeAnimations === 'string') {
2287
2571
  // Single animation type specified
2288
- this.animationSequence = [{ type: themeAnimations }];
2572
+ this.animationSequence = [{ Type: themeAnimations }];
2289
2573
  }
2290
2574
  else {
2291
2575
  // Array of animation steps
@@ -2293,7 +2577,7 @@ export class ShellComponent extends BaseAngularComponent {
2293
2577
  }
2294
2578
  // Set initial animation
2295
2579
  if (this.animationSequence.length > 0) {
2296
- this.currentLoadingAnimation = this.animationSequence[0].type;
2580
+ this.CurrentLoadingAnimation = this.animationSequence[0].Type;
2297
2581
  }
2298
2582
  }
2299
2583
  /**
@@ -2317,7 +2601,7 @@ export class ShellComponent extends BaseAngularComponent {
2317
2601
  return;
2318
2602
  }
2319
2603
  const currentStep = this.animationSequence[this.currentAnimationIndex];
2320
- const durationMs = currentStep.durationMs;
2604
+ const durationMs = currentStep.DurationMs;
2321
2605
  // If no duration specified (or 0), this step runs indefinitely
2322
2606
  if (!durationMs || durationMs <= 0) {
2323
2607
  return;
@@ -2337,7 +2621,7 @@ export class ShellComponent extends BaseAngularComponent {
2337
2621
  return;
2338
2622
  }
2339
2623
  const nextStep = this.animationSequence[this.currentAnimationIndex];
2340
- this.currentLoadingAnimation = nextStep.type;
2624
+ this.CurrentLoadingAnimation = nextStep.Type;
2341
2625
  this.cdr.detectChanges();
2342
2626
  // Schedule the next transition if this step has a duration
2343
2627
  this.scheduleNextAnimationStep();
@@ -2354,11 +2638,11 @@ export class ShellComponent extends BaseAngularComponent {
2354
2638
  /**
2355
2639
  * Handle app switch from app switcher
2356
2640
  */
2357
- async onAppSwitch(appId) {
2641
+ async OnAppSwitch(appId) {
2358
2642
  // Clear the system tab flag since we're switching to a real app
2359
- this.isViewingSystemTab = false;
2643
+ this.IsViewingSystemTab = false;
2360
2644
  // Show loading indicator in app switcher
2361
- this.loadingAppId = appId;
2645
+ this.LoadingAppId = appId;
2362
2646
  this.cdr.detectChanges();
2363
2647
  try {
2364
2648
  const app = this.appManager.GetAppById(appId);
@@ -2367,11 +2651,11 @@ export class ShellComponent extends BaseAngularComponent {
2367
2651
  const systemApp = this.appManager.GetAllSystemApps().find(a => UUIDsEqual(a.ID, appId));
2368
2652
  const appName = systemApp?.Name || 'this application';
2369
2653
  // Clear loading indicator before showing dialog
2370
- this.loadingAppId = null;
2654
+ this.LoadingAppId = null;
2371
2655
  this.cdr.detectChanges();
2372
2656
  // Show "Add Application?" dialog
2373
- if (this.appAccessDialog) {
2374
- this.appAccessDialog.show({
2657
+ if (this.AppAccessDialog) {
2658
+ this.AppAccessDialog.show({
2375
2659
  type: 'not_installed',
2376
2660
  appName: appName,
2377
2661
  appId: appId
@@ -2423,10 +2707,14 @@ export class ShellComponent extends BaseAngularComponent {
2423
2707
  }
2424
2708
  finally {
2425
2709
  // Clear loading indicator
2426
- this.loadingAppId = null;
2710
+ this.LoadingAppId = null;
2427
2711
  this.cdr.detectChanges();
2428
2712
  }
2429
2713
  }
2714
+ /** @deprecated Use {@link OnAppSwitch}. */
2715
+ async onAppSwitch(appId) {
2716
+ return this.OnAppSwitch(appId);
2717
+ }
2430
2718
  /**
2431
2719
  * Find an existing tab that matches the default nav item
2432
2720
  */
@@ -2454,22 +2742,30 @@ export class ShellComponent extends BaseAngularComponent {
2454
2742
  /**
2455
2743
  * Handle Nav Bar app icon click (single click switches to app)
2456
2744
  */
2457
- onNavBarAppClick(app, event) {
2745
+ OnNavBarAppClick(app, event) {
2458
2746
  // If shift key is held, force new tab
2459
2747
  if (event.shiftKey) {
2460
2748
  this.openNavBarAppInNewTab(app);
2461
2749
  }
2462
2750
  else {
2463
- this.onAppSwitch(app.ID);
2751
+ this.OnAppSwitch(app.ID);
2464
2752
  }
2465
2753
  }
2754
+ /** @deprecated Use {@link OnNavBarAppClick}. */
2755
+ onNavBarAppClick(app, event) {
2756
+ return this.OnNavBarAppClick(app, event);
2757
+ }
2466
2758
  /**
2467
2759
  * Handle Nav Bar app icon double-click (opens in new tab)
2468
2760
  */
2469
- onNavBarAppDblClick(app, event) {
2761
+ OnNavBarAppDblClick(app, event) {
2470
2762
  event.preventDefault();
2471
2763
  this.openNavBarAppInNewTab(app);
2472
2764
  }
2765
+ /** @deprecated Use {@link OnNavBarAppDblClick}. */
2766
+ onNavBarAppDblClick(app, event) {
2767
+ return this.OnNavBarAppDblClick(app, event);
2768
+ }
2473
2769
  /**
2474
2770
  * Open a nav bar app's default content in a new tab
2475
2771
  */
@@ -2478,7 +2774,7 @@ export class ShellComponent extends BaseAngularComponent {
2478
2774
  const tabRequest = await app.CreateDefaultTab();
2479
2775
  if (tabRequest) {
2480
2776
  // Set the app as active first if it isn't already
2481
- if (!UUIDsEqual(this.activeApp?.ID, app.ID)) {
2777
+ if (!UUIDsEqual(this.ActiveApp?.ID, app.ID)) {
2482
2778
  await this.appManager.SetActiveApp(app.ID);
2483
2779
  }
2484
2780
  // Open in new tab by adding to workspace
@@ -2488,49 +2784,65 @@ export class ShellComponent extends BaseAngularComponent {
2488
2784
  /**
2489
2785
  * Handle navigation item click with shift-key detection
2490
2786
  */
2491
- onNavItemClick(event) {
2492
- if (!this.activeApp) {
2787
+ OnNavItemClick(event) {
2788
+ if (!this.ActiveApp) {
2493
2789
  return;
2494
2790
  }
2495
2791
  const { item, shiftKey } = event;
2496
2792
  // Close mobile nav if open. Routed through closeMobileNav() rather than setting the flag
2497
2793
  // directly so focus does not get dropped to <body> when the drawer goes inert — tapping a
2498
2794
  // nav item is the most common way the drawer closes on a phone.
2499
- this.closeMobileNav();
2795
+ this.CloseMobileNav();
2500
2796
  // Use NavigationService with forceNewTab option if shift was pressed
2501
- this.navigationService.OpenNavItem(this.activeApp.ID, item, this.activeApp.GetColor(), { forceNewTab: shiftKey });
2797
+ this.navigationService.OpenNavItem(this.ActiveApp.ID, item, this.ActiveApp.GetColor(), { forceNewTab: shiftKey });
2798
+ }
2799
+ /** @deprecated Use {@link OnNavItemClick}. */
2800
+ onNavItemClick(event) {
2801
+ return this.OnNavItemClick(event);
2502
2802
  }
2503
2803
  /**
2504
2804
  * Handle dismiss of a dynamic nav item (remove from recent stack)
2505
2805
  */
2506
- onNavItemDismiss(item) {
2507
- if (!this.activeApp) {
2806
+ OnNavItemDismiss(item) {
2807
+ if (!this.ActiveApp) {
2508
2808
  return;
2509
2809
  }
2510
2810
  // Delegate to HomeApplication's RemoveDynamicNavItem if available
2511
- const appWithRemove = this.activeApp;
2811
+ const appWithRemove = this.ActiveApp;
2512
2812
  if (typeof appWithRemove.RemoveDynamicNavItem === 'function') {
2513
2813
  appWithRemove.RemoveDynamicNavItem(item);
2514
2814
  }
2515
2815
  }
2816
+ /** @deprecated Use {@link OnNavItemDismiss}. */
2817
+ onNavItemDismiss(item) {
2818
+ return this.OnNavItemDismiss(item);
2819
+ }
2516
2820
  /**
2517
2821
  * Toggle mobile navigation drawer
2518
2822
  */
2519
- toggleMobileNav() {
2520
- this.mobileNavOpen = !this.mobileNavOpen;
2521
- if (!this.mobileNavOpen) {
2823
+ ToggleMobileNav() {
2824
+ this.MobileNavOpen = !this.MobileNavOpen;
2825
+ if (!this.MobileNavOpen) {
2522
2826
  this.returnFocusFromMobileNav();
2523
2827
  }
2524
2828
  }
2829
+ /** @deprecated Use {@link ToggleMobileNav}. */
2830
+ toggleMobileNav() {
2831
+ return this.ToggleMobileNav();
2832
+ }
2525
2833
  /**
2526
2834
  * Close mobile navigation drawer
2527
2835
  */
2528
- closeMobileNav() {
2529
- if (this.mobileNavOpen) {
2530
- this.mobileNavOpen = false;
2836
+ CloseMobileNav() {
2837
+ if (this.MobileNavOpen) {
2838
+ this.MobileNavOpen = false;
2531
2839
  this.returnFocusFromMobileNav();
2532
2840
  }
2533
2841
  }
2842
+ /** @deprecated Use {@link CloseMobileNav}. */
2843
+ closeMobileNav() {
2844
+ return this.CloseMobileNav();
2845
+ }
2534
2846
  /**
2535
2847
  * When the drawer closes it becomes `inert`, which drops focus to <body> if focus was
2536
2848
  * inside it — the user's place in the page is gone and the next Tab restarts at the top.
@@ -2538,12 +2850,12 @@ export class ShellComponent extends BaseAngularComponent {
2538
2850
  * (e.g. the drawer was closed from a keyboard shortcut while the user was in the content).
2539
2851
  */
2540
2852
  returnFocusFromMobileNav() {
2541
- const drawer = this.mobileNavDrawer?.nativeElement;
2853
+ const drawer = this.MobileNavDrawer?.nativeElement;
2542
2854
  const active = document.activeElement;
2543
2855
  if (!drawer || !active || !drawer.contains(active)) {
2544
2856
  return;
2545
2857
  }
2546
- this.mobileNavToggle?.nativeElement.focus();
2858
+ this.MobileNavToggle?.nativeElement.focus();
2547
2859
  }
2548
2860
  /**
2549
2861
  * Accessible name for the avatar button. The name must never depend on the avatar image
@@ -2551,15 +2863,19 @@ export class ShellComponent extends BaseAngularComponent {
2551
2863
  * name of its own — so it is set on the BUTTON and falls back to a generic label before
2552
2864
  * the user record has loaded.
2553
2865
  */
2554
- get avatarAriaLabel() {
2866
+ get AvatarAriaLabel() {
2555
2867
  return this.userName ? `Account: ${this.userName}` : 'Account';
2556
2868
  }
2869
+ /** @deprecated Use {@link AvatarAriaLabel}. */
2870
+ get avatarAriaLabel() {
2871
+ return this.AvatarAriaLabel;
2872
+ }
2557
2873
  /**
2558
2874
  * Skip link target handoff (WCAG 2.4.1). Focus is moved programmatically rather than left
2559
2875
  * to the browser's fragment navigation: the shell's main region is a Golden Layout host,
2560
2876
  * and letting the URL hash change here would collide with the deep-link/tab restore path.
2561
2877
  */
2562
- skipToMainContent(event) {
2878
+ SkipToMainContent(event) {
2563
2879
  event.preventDefault();
2564
2880
  const main = document.getElementById('mj-main-content');
2565
2881
  if (!main) {
@@ -2568,18 +2884,22 @@ export class ShellComponent extends BaseAngularComponent {
2568
2884
  main.focus();
2569
2885
  main.scrollIntoView({ block: 'start' });
2570
2886
  }
2887
+ /** @deprecated Use {@link SkipToMainContent}. */
2888
+ skipToMainContent(event) {
2889
+ return this.SkipToMainContent(event);
2890
+ }
2571
2891
  /**
2572
2892
  * Toggle user menu visibility
2573
2893
  */
2574
- toggleUserMenu(event) {
2894
+ ToggleUserMenu(event) {
2575
2895
  event.stopPropagation();
2576
- this.userMenuVisible = !this.userMenuVisible;
2577
- if (this.userMenuVisible) {
2896
+ this.UserMenuVisible = !this.UserMenuVisible;
2897
+ if (this.UserMenuVisible) {
2578
2898
  // Close menu when clicking outside. CAPTURE phase so clicks whose
2579
2899
  // bubbling something stopped (e.g. the origin crumb's GL-focus
2580
2900
  // stoppers) still dismiss the menu.
2581
2901
  const closeHandler = () => {
2582
- this.userMenuVisible = false;
2902
+ this.UserMenuVisible = false;
2583
2903
  document.removeEventListener('click', closeHandler, true);
2584
2904
  };
2585
2905
  setTimeout(() => {
@@ -2587,6 +2907,10 @@ export class ShellComponent extends BaseAngularComponent {
2587
2907
  }, 0);
2588
2908
  }
2589
2909
  }
2910
+ /** @deprecated Use {@link ToggleUserMenu}. */
2911
+ toggleUserMenu(event) {
2912
+ return this.ToggleUserMenu(event);
2913
+ }
2590
2914
  /**
2591
2915
  * Initialize the user menu plugin system
2592
2916
  */
@@ -2599,7 +2923,7 @@ export class ShellComponent extends BaseAngularComponent {
2599
2923
  }
2600
2924
  // Initialize developer mode service
2601
2925
  if (this.userEntity) {
2602
- await this.developerModeService.Initialize(this.userEntity);
2926
+ await this.DeveloperModeService.Initialize(this.userEntity);
2603
2927
  }
2604
2928
  // Check org-level feedback kill switch (defaults to enabled on error)
2605
2929
  const feedbackEnabled = await this.feedbackService.IsEnabled();
@@ -2609,9 +2933,9 @@ export class ShellComponent extends BaseAngularComponent {
2609
2933
  userEntity: this.userEntity,
2610
2934
  shell: this,
2611
2935
  viewContainerRef: this.viewContainerRef,
2612
- isDeveloper: this.developerModeService.IsDeveloper,
2613
- developerModeEnabled: this.developerModeService.IsEnabled,
2614
- currentApplication: this.activeApp,
2936
+ isDeveloper: this.DeveloperModeService.IsDeveloper,
2937
+ developerModeEnabled: this.DeveloperModeService.IsEnabled,
2938
+ currentApplication: this.ActiveApp,
2615
2939
  workspaceManager: this.workspaceManager,
2616
2940
  authService: this.authBase,
2617
2941
  pinService: this.homePinService,
@@ -2628,11 +2952,11 @@ export class ShellComponent extends BaseAngularComponent {
2628
2952
  // Get initial menu elements
2629
2953
  this.refreshMenuElements();
2630
2954
  // Subscribe to developer mode changes to refresh menu
2631
- this.developerModeService.IsEnabled$.pipe(takeUntil(this.destroy$)).subscribe(() => {
2955
+ this.DeveloperModeService.IsEnabled$.pipe(takeUntil(this.destroy$)).subscribe(() => {
2632
2956
  // Update context and refresh menu
2633
2957
  if (this.userMenu) {
2634
2958
  this.userMenu.UpdateContext({
2635
- developerModeEnabled: this.developerModeService.IsEnabled
2959
+ developerModeEnabled: this.DeveloperModeService.IsEnabled
2636
2960
  });
2637
2961
  }
2638
2962
  this.refreshMenuElements();
@@ -2656,13 +2980,13 @@ export class ShellComponent extends BaseAngularComponent {
2656
2980
  */
2657
2981
  refreshMenuElements() {
2658
2982
  if (this.userMenu) {
2659
- this.userMenuElements = this.userMenu.GetMenuElements();
2983
+ this.UserMenuElements = this.userMenu.GetMenuElements();
2660
2984
  }
2661
2985
  }
2662
2986
  /**
2663
2987
  * Handle user menu item click
2664
2988
  */
2665
- async onUserMenuItemClick(itemId) {
2989
+ async OnUserMenuItemClick(itemId) {
2666
2990
  if (!this.userMenu)
2667
2991
  return;
2668
2992
  const result = await this.userMenu.HandleItemClick(itemId);
@@ -2683,12 +3007,12 @@ export class ShellComponent extends BaseAngularComponent {
2683
3007
  }
2684
3008
  }
2685
3009
  if (result.message === 'reset-layout') {
2686
- await this.onResetLayout();
3010
+ await this.OnResetLayout();
2687
3011
  return;
2688
3012
  }
2689
3013
  if (result.message === 'pin-to-home') {
2690
3014
  // Close menu and show overlay immediately before any async work
2691
- this.userMenuVisible = false;
3015
+ this.UserMenuVisible = false;
2692
3016
  this.refreshMenuElements();
2693
3017
  this.showPinProgress('Pinning...');
2694
3018
  // Let the UI render the overlay before starting the work
@@ -2707,38 +3031,42 @@ export class ShellComponent extends BaseAngularComponent {
2707
3031
  return;
2708
3032
  }
2709
3033
  if (result.message === 'sharing-center') {
2710
- this.userMenuVisible = false;
3034
+ this.UserMenuVisible = false;
2711
3035
  this.userSharingCenterDialogService.open(this.viewContainerRef);
2712
3036
  return;
2713
3037
  }
2714
3038
  if (result.message === 'submit-feedback') {
2715
- this.userMenuVisible = false;
3039
+ this.UserMenuVisible = false;
2716
3040
  this.ShowFeedbackDialog();
2717
3041
  return;
2718
3042
  }
2719
3043
  if (result.message === 'about') {
2720
- this.userMenuVisible = false;
3044
+ this.UserMenuVisible = false;
2721
3045
  this.aboutDialogService.open(this.viewContainerRef, {
2722
- avatarUrl: this.userImageURL || null,
2723
- avatarIconClass: this.userIconClass || null
3046
+ avatarUrl: this.UserImageURL || null,
3047
+ avatarIconClass: this.UserIconClass || null
2724
3048
  });
2725
3049
  return;
2726
3050
  }
2727
3051
  if (result.message === 'profile') {
2728
- this.userMenuVisible = false;
3052
+ this.UserMenuVisible = false;
2729
3053
  this.profileDialogService.open(this.viewContainerRef, {
2730
- avatarUrl: this.userImageURL || null,
2731
- avatarIconClass: this.userIconClass || null
3054
+ avatarUrl: this.UserImageURL || null,
3055
+ avatarIconClass: this.UserIconClass || null
2732
3056
  });
2733
3057
  return;
2734
3058
  }
2735
3059
  if (result.closeMenu) {
2736
- this.userMenuVisible = false;
3060
+ this.UserMenuVisible = false;
2737
3061
  }
2738
3062
  // Refresh menu elements (some items may have changed state)
2739
3063
  this.refreshMenuElements();
2740
3064
  this.cdr.detectChanges();
2741
3065
  }
3066
+ /** @deprecated Use {@link OnUserMenuItemClick}. */
3067
+ async onUserMenuItemClick(itemId) {
3068
+ return this.OnUserMenuItemClick(itemId);
3069
+ }
2742
3070
  /**
2743
3071
  * Whether the currently applied theme is dark (used by the toggle switch in the template)
2744
3072
  */
@@ -2749,28 +3077,44 @@ export class ShellComponent extends BaseAngularComponent {
2749
3077
  /**
2750
3078
  * Get user menu options for template
2751
3079
  */
2752
- getUserMenuOptions() {
3080
+ GetUserMenuOptions() {
2753
3081
  return this.userMenu?.GetOptions();
2754
3082
  }
3083
+ /** @deprecated Use {@link GetUserMenuOptions}. */
3084
+ getUserMenuOptions() {
3085
+ return this.GetUserMenuOptions();
3086
+ }
2755
3087
  /**
2756
3088
  * Get user display info for template
2757
3089
  */
2758
- getUserDisplayInfo() {
3090
+ GetUserDisplayInfo() {
2759
3091
  return this.userMenu?.GetUserDisplayInfo();
2760
3092
  }
3093
+ /** @deprecated Use {@link GetUserDisplayInfo}. */
3094
+ getUserDisplayInfo() {
3095
+ return this.GetUserDisplayInfo();
3096
+ }
2761
3097
  /**
2762
3098
  * Check if an element is a divider (for template)
2763
3099
  */
2764
- isMenuDivider(element) {
3100
+ IsMenuDivider(element) {
2765
3101
  return isUserMenuDivider(element);
2766
3102
  }
3103
+ /** @deprecated Use {@link IsMenuDivider}. */
3104
+ isMenuDivider(element) {
3105
+ return this.IsMenuDivider(element);
3106
+ }
2767
3107
  /**
2768
3108
  * Cast element to UserMenuItem (for template type safety)
2769
3109
  * Call this only after checking !isMenuDivider(element)
2770
3110
  */
2771
- asMenuItem(element) {
3111
+ AsMenuItem(element) {
2772
3112
  return element;
2773
3113
  }
3114
+ /** @deprecated Use {@link AsMenuItem}. */
3115
+ asMenuItem(element) {
3116
+ return this.AsMenuItem(element);
3117
+ }
2774
3118
  /**
2775
3119
  * Pin the currently active resource to the Home dashboard
2776
3120
  */
@@ -2797,10 +3141,10 @@ export class ShellComponent extends BaseAngularComponent {
2797
3141
  }
2798
3142
  // Resolve nav item icon for Custom pins
2799
3143
  let pinIcon;
2800
- if (resourceType === 'Custom' && this.activeApp) {
3144
+ if (resourceType === 'Custom' && this.ActiveApp) {
2801
3145
  const navItemName = activeTab.configuration?.['navItemName'];
2802
3146
  if (navItemName) {
2803
- const navItems = await this.activeApp.GetNavItems();
3147
+ const navItems = await this.ActiveApp.GetNavItems();
2804
3148
  const navItem = navItems.find(ni => ni.Label === navItemName);
2805
3149
  pinIcon = navItem?.Icon || undefined;
2806
3150
  }
@@ -2809,9 +3153,9 @@ export class ShellComponent extends BaseAngularComponent {
2809
3153
  DisplayName: displayName,
2810
3154
  ResourceType: resourceType,
2811
3155
  ApplicationID: activeTab.applicationId,
2812
- ApplicationName: this.activeApp?.Name,
3156
+ ApplicationName: this.ActiveApp?.Name,
2813
3157
  Icon: pinIcon,
2814
- Color: this.activeApp?.GetColor() || undefined,
3158
+ Color: this.ActiveApp?.GetColor() || undefined,
2815
3159
  Configuration: activeTab.configuration
2816
3160
  });
2817
3161
  if (added) {
@@ -2831,9 +3175,9 @@ export class ShellComponent extends BaseAngularComponent {
2831
3175
  */
2832
3176
  async captureAndAttachThumbnail(tab, resourceType) {
2833
3177
  try {
2834
- if (!this.tabContainerRef)
3178
+ if (!this.TabContainerRef)
2835
3179
  return;
2836
- const thumbnail = await this.tabContainerRef.CaptureActiveThumbnail();
3180
+ const thumbnail = await this.TabContainerRef.CaptureActiveThumbnail();
2837
3181
  if (thumbnail) {
2838
3182
  const pin = this.homePinService.FindPin(resourceType, tab.configuration);
2839
3183
  if (pin) {
@@ -2905,19 +3249,23 @@ export class ShellComponent extends BaseAngularComponent {
2905
3249
  /**
2906
3250
  * Log current workspace configuration to console (debug)
2907
3251
  */
2908
- onLogLayout() {
3252
+ OnLogLayout() {
2909
3253
  const config = this.workspaceManager.GetConfiguration();
2910
3254
  console.log('📋 Workspace Configuration:', JSON.stringify(config, null, 2));
2911
3255
  console.log('📋 Workspace Configuration (object):', config);
2912
- this.userMenuVisible = false;
3256
+ this.UserMenuVisible = false;
3257
+ }
3258
+ /** @deprecated Use {@link OnLogLayout}. */
3259
+ onLogLayout() {
3260
+ return this.OnLogLayout();
2913
3261
  }
2914
3262
  /**
2915
3263
  * Reset workspace layout - clears all tabs and switches to single-resource mode
2916
3264
  */
2917
- async onResetLayout() {
2918
- this.userMenuVisible = false;
3265
+ async OnResetLayout() {
3266
+ this.UserMenuVisible = false;
2919
3267
  // Get current active app to create a fresh default tab
2920
- const currentApp = this.activeApp;
3268
+ const currentApp = this.ActiveApp;
2921
3269
  if (!currentApp) {
2922
3270
  console.warn('No active app to reset to');
2923
3271
  return;
@@ -2961,42 +3309,22 @@ export class ShellComponent extends BaseAngularComponent {
2961
3309
  // Update workspace configuration
2962
3310
  this.workspaceManager.UpdateConfiguration(freshConfig);
2963
3311
  }
3312
+ /** @deprecated Use {@link OnResetLayout}. */
3313
+ async onResetLayout() {
3314
+ return this.OnResetLayout();
3315
+ }
2964
3316
  /**
2965
3317
  * Logout user and clear authentication data
2966
3318
  */
2967
- async onLogout() {
2968
- this.userMenuVisible = false;
3319
+ async OnLogout() {
3320
+ this.UserMenuVisible = false;
2969
3321
  this.authBase.logout();
2970
3322
  localStorage.removeItem('auth');
2971
3323
  localStorage.removeItem('claims');
2972
3324
  }
2973
- /**
2974
- * Global keyboard shortcut handler
2975
- * Cmd+/ (Mac) or Ctrl+/ (Windows) opens the command palette
2976
- */
2977
- handleGlobalKeyboardShortcuts(event) {
2978
- // Skip if user is typing in an input/textarea — EXCEPT when the omnibar is on:
2979
- // a modal palette is summonable from anywhere (incl. the chat composer), like
2980
- // Slack/Linear. The legacy path keeps the guard (its binding steals focus).
2981
- const target = event.target;
2982
- const inEditable = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
2983
- if (inEditable && !this.UseOmnibar) {
2984
- return;
2985
- }
2986
- // Platform detection
2987
- const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
2988
- const isCtrlOrCmd = isMac ? event.metaKey : event.ctrlKey;
2989
- // Cmd+/ or Ctrl+/ opens the palette (omnibar '/' mode when enabled, legacy otherwise)
2990
- if (isCtrlOrCmd && event.key === '/') {
2991
- event.preventDefault();
2992
- event.stopPropagation();
2993
- if (this.UseOmnibar) {
2994
- this.OpenOmnibar('/');
2995
- }
2996
- else {
2997
- this.commandPaletteService.Open();
2998
- }
2999
- }
3325
+ /** @deprecated Use {@link OnLogout}. */
3326
+ async onLogout() {
3327
+ return this.OnLogout();
3000
3328
  }
3001
3329
  /**
3002
3330
  * Load user avatar from database, auto-sync from auth provider if needed
@@ -3005,7 +3333,7 @@ export class ShellComponent extends BaseAngularComponent {
3005
3333
  try {
3006
3334
  const md = this.ProviderToUse;
3007
3335
  this.userName = currentUserInfo.FirstLast || currentUserInfo.Name || 'User';
3008
- this.userEmail = currentUserInfo.Email || '';
3336
+ this.UserEmail = currentUserInfo.Email || '';
3009
3337
  // Load the full MJUserEntity to access avatar fields
3010
3338
  const currentUserEntity = await md.GetEntityObject('MJ: Users');
3011
3339
  await currentUserEntity.Load(currentUserInfo.ID);
@@ -3025,8 +3353,8 @@ export class ShellComponent extends BaseAngularComponent {
3025
3353
  catch (error) {
3026
3354
  console.warn('Could not load user avatar:', error);
3027
3355
  // Use fallback
3028
- this.userImageURL = '';
3029
- this.userIconClass = null;
3356
+ this.UserImageURL = '';
3357
+ this.UserIconClass = null;
3030
3358
  }
3031
3359
  }
3032
3360
  /**
@@ -3053,17 +3381,17 @@ export class ShellComponent extends BaseAngularComponent {
3053
3381
  */
3054
3382
  applyUserAvatar(user) {
3055
3383
  if (user.UserImageURL) {
3056
- this.userImageURL = user.UserImageURL;
3057
- this.userIconClass = null;
3384
+ this.UserImageURL = user.UserImageURL;
3385
+ this.UserIconClass = null;
3058
3386
  }
3059
3387
  else if (user.UserImageIconClass) {
3060
- this.userIconClass = user.UserImageIconClass;
3061
- this.userImageURL = '';
3388
+ this.UserIconClass = user.UserImageIconClass;
3389
+ this.UserImageURL = '';
3062
3390
  }
3063
3391
  else {
3064
3392
  // Default fallback - show icon
3065
- this.userImageURL = '';
3066
- this.userIconClass = null;
3393
+ this.UserImageURL = '';
3394
+ this.UserIconClass = null;
3067
3395
  }
3068
3396
  this.cdr.detectChanges();
3069
3397
  }
@@ -3075,7 +3403,7 @@ export class ShellComponent extends BaseAngularComponent {
3075
3403
  // Find the active tab
3076
3404
  const activeTab = config.tabs?.find(tab => tab.id === config.activeTabId);
3077
3405
  // Get app name
3078
- const appName = this.activeApp?.Name || null;
3406
+ const appName = this.ActiveApp?.Name || null;
3079
3407
  // Get resource name from active tab
3080
3408
  let resourceName = null;
3081
3409
  if (activeTab) {
@@ -3092,15 +3420,15 @@ export class ShellComponent extends BaseAngularComponent {
3092
3420
  */
3093
3421
  async loadSearchableEntities() {
3094
3422
  const md = this.ProviderToUse;
3095
- this.searchableEntities = md.Entities.filter((e) => e.AllowUserSearchAPI).sort((a, b) => a.Name.localeCompare(b.Name));
3096
- if (this.searchableEntities.length > 0) {
3097
- this.selectedEntity = this.searchableEntities[0];
3423
+ this.SearchableEntities = md.Entities.filter((e) => e.AllowUserSearchAPI).sort((a, b) => a.Name.localeCompare(b.Name));
3424
+ if (this.SearchableEntities.length > 0) {
3425
+ this.SelectedEntity = this.SearchableEntities[0];
3098
3426
  }
3099
3427
  }
3100
3428
  /**
3101
3429
  * Toggle search popup visibility
3102
3430
  */
3103
- toggleSearch() {
3431
+ ToggleSearch() {
3104
3432
  if (!this.ShowSearchBar) {
3105
3433
  return;
3106
3434
  }
@@ -3108,36 +3436,44 @@ export class ShellComponent extends BaseAngularComponent {
3108
3436
  this.OpenOmnibar();
3109
3437
  return;
3110
3438
  }
3111
- this.isSearchOpen = !this.isSearchOpen;
3439
+ this.IsSearchOpen = !this.IsSearchOpen;
3112
3440
  // Focus on search input when opened
3113
- if (this.isSearchOpen) {
3441
+ if (this.IsSearchOpen) {
3114
3442
  setTimeout(() => {
3115
- if (this.searchInput && this.searchInput.nativeElement) {
3116
- this.searchInput.nativeElement.focus();
3443
+ if (this.SearchInput && this.SearchInput.nativeElement) {
3444
+ this.SearchInput.nativeElement.focus();
3117
3445
  }
3118
3446
  }, 100);
3119
3447
  }
3120
3448
  }
3449
+ /** @deprecated Use {@link ToggleSearch}. */
3450
+ toggleSearch() {
3451
+ return this.ToggleSearch();
3452
+ }
3121
3453
  /**
3122
3454
  * Close search popup
3123
3455
  */
3456
+ CloseSearch() {
3457
+ this.IsSearchOpen = false;
3458
+ }
3459
+ /** @deprecated Use {@link CloseSearch}. */
3124
3460
  closeSearch() {
3125
- this.isSearchOpen = false;
3461
+ return this.CloseSearch();
3126
3462
  }
3127
3463
  /**
3128
3464
  * Handle search submission
3129
3465
  */
3130
- onSearch(event) {
3131
- if (!this.searchInput) {
3466
+ OnSearch(event) {
3467
+ if (!this.SearchInput) {
3132
3468
  return;
3133
3469
  }
3134
- const inputValue = this.searchInput.nativeElement.value;
3470
+ const inputValue = this.SearchInput.nativeElement.value;
3135
3471
  if (inputValue && inputValue.length > 0 && inputValue.trim().length > 2) {
3136
- this.searchInput.nativeElement.value = ''; // Clear input
3137
- this.isSearchOpen = false; // Close search popup
3472
+ this.SearchInput.nativeElement.value = ''; // Clear input
3473
+ this.IsSearchOpen = false; // Close search popup
3138
3474
  // Navigate to search results
3139
- if (this.selectedEntity) {
3140
- this.router.navigate(['resource', 'search', inputValue], { queryParams: { Entity: this.selectedEntity.Name } });
3475
+ if (this.SelectedEntity) {
3476
+ this.router.navigate(['resource', 'search', inputValue], { queryParams: { Entity: this.SelectedEntity.Name } });
3141
3477
  }
3142
3478
  }
3143
3479
  else {
@@ -3154,23 +3490,60 @@ export class ShellComponent extends BaseAngularComponent {
3154
3490
  });
3155
3491
  }
3156
3492
  }
3493
+ /** @deprecated Use {@link OnSearch}. */
3494
+ onSearch(event) {
3495
+ return this.OnSearch(event);
3496
+ }
3157
3497
  // ========================================
3158
3498
  // UNIVERSAL SEARCH EVENT HANDLERS
3159
3499
  // ========================================
3500
+ /**
3501
+ * The shell's ONLY `document:keydown` listener — handles every global chord.
3502
+ *
3503
+ * ⚠️ Do NOT add a second `@HostListener('document:keydown')` to this component.
3504
+ * Angular's compiler collects host listeners into an object keyed by event name,
3505
+ * so a second declaration for the same event SILENTLY REPLACES the first — no
3506
+ * build error, no warning, the earlier handler simply never registers. That is
3507
+ * exactly what happened here: a separate `handleGlobalKeyboardShortcuts` owned
3508
+ * Ctrl+/ and was overwritten by this method, so the command palette shortcut was
3509
+ * dead for its entire life. Every new global chord belongs in this one method.
3510
+ *
3511
+ * - **Ctrl/Cmd+K** summons search. Omnibar on: the modal palette opens from
3512
+ * anywhere, even while typing (explicit chord, Slack/Linear semantics). Omnibar
3513
+ * off: the chord FOCUSES the inline header composite on desktop (results attach
3514
+ * beneath it), so keep the don't-steal-focus-mid-typing guard; on mobile (no
3515
+ * composite rendered) it opens the Spotlight overlay instead.
3516
+ * - **Ctrl/Cmd+/** opens the command palette, even from an input — the chord can
3517
+ * never be mistaken for ordinary typing. It yields only when an element under
3518
+ * focus already handled it (`defaultPrevented`): CodeMirror binds Mod-/ to toggle
3519
+ * comment and calls `preventDefault()` without stopping propagation.
3520
+ */
3160
3521
  OnGlobalKeydown(event) {
3161
- // Ctrl/Cmd+K summons search. Omnibar: the modal palette opens from anywhere,
3162
- // even while typing (explicit chord, Slack/Linear semantics). Legacy: the
3163
- // chord FOCUSES the inline header composite on desktop (results attach
3164
- // beneath it), so keep the don't-steal-focus-mid-typing guard; on mobile
3165
- // (no composite rendered) it opens the Spotlight overlay instead.
3166
- const target = event.target;
3167
- const inEditable = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
3168
- if (inEditable && !this.UseOmnibar) {
3169
- return;
3170
- }
3171
3522
  const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
3172
3523
  const isCtrlOrCmd = isMac ? event.metaKey : event.ctrlKey;
3173
- if (isCtrlOrCmd && event.key === 'k') {
3524
+ if (!isCtrlOrCmd) {
3525
+ return;
3526
+ }
3527
+ if (event.key === '/') {
3528
+ if (event.defaultPrevented) {
3529
+ return;
3530
+ }
3531
+ event.preventDefault();
3532
+ event.stopPropagation();
3533
+ if (this.UseOmnibar) {
3534
+ this.OpenOmnibar('/');
3535
+ }
3536
+ else {
3537
+ this.commandPaletteService.Open();
3538
+ }
3539
+ return;
3540
+ }
3541
+ if (event.key === 'k') {
3542
+ const target = event.target;
3543
+ const inEditable = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
3544
+ if (inEditable && !this.UseOmnibar) {
3545
+ return;
3546
+ }
3174
3547
  event.preventDefault();
3175
3548
  event.stopPropagation();
3176
3549
  this.OnHeaderSearchClick();
@@ -3190,10 +3563,10 @@ export class ShellComponent extends BaseAngularComponent {
3190
3563
  if (this.UseOmnibar) {
3191
3564
  this.OpenOmnibar();
3192
3565
  }
3193
- else if (!isMobile && this.shellSearchComposite?.Focus) {
3566
+ else if (!isMobile && this.ShellSearchComposite?.Focus) {
3194
3567
  // Omnibar-off desktop: the inline composite is on screen — focus it and
3195
3568
  // let its attached suggest dropdown do the work.
3196
- this.shellSearchComposite.Focus();
3569
+ this.ShellSearchComposite.Focus();
3197
3570
  }
3198
3571
  else {
3199
3572
  // Omnibar-off mobile: no visible inline composite — open the Spotlight overlay.
@@ -3235,12 +3608,20 @@ export class ShellComponent extends BaseAngularComponent {
3235
3608
  this.LegacySearchOpen = false;
3236
3609
  this.navigationService.OpenSearch(query);
3237
3610
  }
3611
+ /** Command palette "Search everything" row → full Search Results workspace. Does
3612
+ nothing while the chrome hides search, like the other search entry points. */
3613
+ OnCommandPaletteSearch(query) {
3614
+ if (!this.ShowSearchBar || !query.trim()) {
3615
+ return;
3616
+ }
3617
+ this.navigationService.OpenSearch(query);
3618
+ }
3238
3619
  /** Inline composite submit (Enter) → full Search Results workspace, carrying the
3239
3620
  composite's relevance/scope selections. */
3240
3621
  OnSearchSubmitted(query) {
3241
3622
  if (query && query.trim().length >= 2) {
3242
- const minRelevance = this.shellSearchComposite?.MinRelevancePercent;
3243
- const scopeIDs = this.shellSearchComposite?.SelectedScopeIDs;
3623
+ const minRelevance = this.ShellSearchComposite?.MinRelevancePercent;
3624
+ const scopeIDs = this.ShellSearchComposite?.SelectedScopeIDs;
3244
3625
  const opts = {};
3245
3626
  if (minRelevance)
3246
3627
  opts.minRelevance = minRelevance;
@@ -3260,19 +3641,27 @@ export class ShellComponent extends BaseAngularComponent {
3260
3641
  * Show notifications page as a tab
3261
3642
  */
3262
3643
  /** Toggle the global Activity drawer. */
3263
- toggleActivity(event) {
3644
+ ToggleActivity(event) {
3264
3645
  event.stopPropagation();
3265
- this.activityOpen = !this.activityOpen;
3646
+ this.ActivityOpen = !this.ActivityOpen;
3266
3647
  this.cdr.detectChanges();
3267
3648
  }
3649
+ /** @deprecated Use {@link ToggleActivity}. */
3650
+ toggleActivity(event) {
3651
+ return this.ToggleActivity(event);
3652
+ }
3268
3653
  /** Clear finished activities from the tracker. */
3269
- clearFinishedActivity() {
3654
+ ClearFinishedActivity() {
3270
3655
  this.activityService.ClearFinished();
3271
3656
  }
3657
+ /** @deprecated Use {@link ClearFinishedActivity}. */
3658
+ clearFinishedActivity() {
3659
+ return this.ClearFinishedActivity();
3660
+ }
3272
3661
  /** Close the Activity drawer when clicking anywhere outside it. */
3273
3662
  onDocumentClickCloseActivity() {
3274
- if (this.activityOpen) {
3275
- this.activityOpen = false;
3663
+ if (this.ActivityOpen) {
3664
+ this.ActivityOpen = false;
3276
3665
  this.cdr.detectChanges();
3277
3666
  }
3278
3667
  }
@@ -3322,8 +3711,8 @@ export class ShellComponent extends BaseAngularComponent {
3322
3711
  async captureAndAttachFeedbackScreenshot() {
3323
3712
  try {
3324
3713
  let screenshot;
3325
- if (this.tabContainerRef) {
3326
- screenshot = await this.tabContainerRef.CaptureActiveThumbnail();
3714
+ if (this.TabContainerRef) {
3715
+ screenshot = await this.TabContainerRef.CaptureActiveThumbnail();
3327
3716
  }
3328
3717
  if (!screenshot) {
3329
3718
  const contentEl = document.querySelector('.shell-content')
@@ -3369,8 +3758,8 @@ export class ShellComponent extends BaseAngularComponent {
3369
3758
  // Show the dialog on top of the loading screen
3370
3759
  // Use setTimeout to ensure the dialog component is ready after view init
3371
3760
  setTimeout(() => {
3372
- if (this.appAccessDialog) {
3373
- this.appAccessDialog.show(dialogConfig);
3761
+ if (this.AppAccessDialog) {
3762
+ this.AppAccessDialog.show(dialogConfig);
3374
3763
  }
3375
3764
  else {
3376
3765
  // Fallback if dialog not available - redirect to a working app
@@ -3428,24 +3817,24 @@ export class ShellComponent extends BaseAngularComponent {
3428
3817
  LogStatus('User has no applications available');
3429
3818
  // Stop loading animation and show the dialog
3430
3819
  this.stopLoadingAnimation();
3431
- this.loading = false;
3820
+ this.Loading = false;
3432
3821
  this.cdr.detectChanges();
3433
3822
  setTimeout(() => {
3434
- if (this.appAccessDialog) {
3435
- this.appAccessDialog.show({ type: 'no_apps' });
3823
+ if (this.AppAccessDialog) {
3824
+ this.AppAccessDialog.show({ type: 'no_apps' });
3436
3825
  }
3437
3826
  }, 0);
3438
3827
  }
3439
3828
  /**
3440
3829
  * Handle Golden Layout initialization failure
3441
3830
  */
3442
- handleLayoutError() {
3831
+ HandleLayoutError() {
3443
3832
  LogStatus('Golden Layout initialization failed');
3444
3833
  const availableApps = this.appManager.GetAllApps();
3445
3834
  if (availableApps.length > 0) {
3446
3835
  setTimeout(() => {
3447
- if (this.appAccessDialog) {
3448
- this.appAccessDialog.show({ type: 'layout_error' });
3836
+ if (this.AppAccessDialog) {
3837
+ this.AppAccessDialog.show({ type: 'layout_error' });
3449
3838
  }
3450
3839
  else {
3451
3840
  // Direct redirect if dialog not available
@@ -3454,10 +3843,14 @@ export class ShellComponent extends BaseAngularComponent {
3454
3843
  }, 0);
3455
3844
  }
3456
3845
  }
3846
+ /** @deprecated Use {@link HandleLayoutError}. */
3847
+ handleLayoutError() {
3848
+ return this.HandleLayoutError();
3849
+ }
3457
3850
  /**
3458
3851
  * Handle dialog result (install, enable, or redirect)
3459
3852
  */
3460
- async onAppAccessDialogResult(result) {
3853
+ async OnAppAccessDialogResult(result) {
3461
3854
  const availableApps = this.appManager.GetAllApps();
3462
3855
  switch (result.action) {
3463
3856
  case 'install':
@@ -3477,6 +3870,10 @@ export class ShellComponent extends BaseAngularComponent {
3477
3870
  break;
3478
3871
  }
3479
3872
  }
3873
+ /** @deprecated Use {@link OnAppAccessDialogResult}. */
3874
+ async onAppAccessDialogResult(result) {
3875
+ return this.OnAppAccessDialogResult(result);
3876
+ }
3480
3877
  /**
3481
3878
  * Install an app for the user and navigate to it
3482
3879
  */
@@ -3493,13 +3890,13 @@ export class ShellComponent extends BaseAngularComponent {
3493
3890
  }
3494
3891
  else {
3495
3892
  console.error('[ShellComponent] Failed to add application');
3496
- this.appAccessDialog?.completeProcessing();
3893
+ this.AppAccessDialog?.completeProcessing();
3497
3894
  this.redirectToFallbackApp(this.appManager.GetAllApps());
3498
3895
  }
3499
3896
  }
3500
3897
  catch (error) {
3501
3898
  console.error('Error adding app:', error);
3502
- this.appAccessDialog?.completeProcessing();
3899
+ this.AppAccessDialog?.completeProcessing();
3503
3900
  this.redirectToFallbackApp(this.appManager.GetAllApps());
3504
3901
  }
3505
3902
  }
@@ -3514,13 +3911,13 @@ export class ShellComponent extends BaseAngularComponent {
3514
3911
  await this.waitForAppAndNavigate(appId);
3515
3912
  }
3516
3913
  else {
3517
- this.appAccessDialog?.completeProcessing();
3914
+ this.AppAccessDialog?.completeProcessing();
3518
3915
  this.redirectToFallbackApp(this.appManager.GetAllApps());
3519
3916
  }
3520
3917
  }
3521
3918
  catch (error) {
3522
3919
  console.error('Error enabling app:', error);
3523
- this.appAccessDialog?.completeProcessing();
3920
+ this.AppAccessDialog?.completeProcessing();
3524
3921
  this.redirectToFallbackApp(this.appManager.GetAllApps());
3525
3922
  }
3526
3923
  }
@@ -3536,7 +3933,7 @@ export class ShellComponent extends BaseAngularComponent {
3536
3933
  const app = this.appManager.GetAppById(appId);
3537
3934
  if (app) {
3538
3935
  await this.navigateToApp(app);
3539
- this.appAccessDialog?.completeProcessing();
3936
+ this.AppAccessDialog?.completeProcessing();
3540
3937
  return;
3541
3938
  }
3542
3939
  // Wait a bit before checking again
@@ -3546,11 +3943,11 @@ export class ShellComponent extends BaseAngularComponent {
3546
3943
  const systemApp = this.appManager.GetAllSystemApps().find(a => UUIDsEqual(a.ID, appId));
3547
3944
  if (systemApp) {
3548
3945
  await this.navigateToApp(systemApp);
3549
- this.appAccessDialog?.completeProcessing();
3946
+ this.AppAccessDialog?.completeProcessing();
3550
3947
  }
3551
3948
  else {
3552
3949
  console.warn(`[ShellComponent] App ${appId} not found after waiting, redirecting to fallback app`);
3553
- this.appAccessDialog?.completeProcessing();
3950
+ this.AppAccessDialog?.completeProcessing();
3554
3951
  this.redirectToFallbackApp(this.appManager.GetAllApps());
3555
3952
  }
3556
3953
  }
@@ -3571,7 +3968,7 @@ export class ShellComponent extends BaseAngularComponent {
3571
3968
  }
3572
3969
  /** Case-insensitive UUID check whether an app is the currently active app. */
3573
3970
  IsActiveApp(app) {
3574
- return UUIDsEqual(app.ID, this.activeApp?.ID);
3971
+ return UUIDsEqual(app.ID, this.ActiveApp?.ID);
3575
3972
  }
3576
3973
  /**
3577
3974
  * Candidate order for landing and fallback navigation: the declared-default app
@@ -3632,7 +4029,7 @@ export class ShellComponent extends BaseAngularComponent {
3632
4029
  }
3633
4030
  }
3634
4031
  // No apps available - this shouldn't happen, but handle gracefully
3635
- this.loading = false;
4032
+ this.Loading = false;
3636
4033
  this.cdr.detectChanges();
3637
4034
  }
3638
4035
  static ɵfac = function ShellComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShellComponent)(i0.ɵɵdirectiveInject(i1.ApplicationManager), i0.ɵɵdirectiveInject(i1.WorkspaceStateManager), i0.ɵɵdirectiveInject(i1.GoldenLayoutManager), i0.ɵɵdirectiveInject(i1.TabService), i0.ɵɵdirectiveInject(i2.NavigationService), i0.ɵɵdirectiveInject(i3.ActivatedRoute), i0.ɵɵdirectiveInject(i3.Router), i0.ɵɵdirectiveInject(i4.MJAuthBase), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i5.UserAvatarService), i0.ɵɵdirectiveInject(i6.UserSharingCenterDialogService), i0.ɵɵdirectiveInject(i7.AboutDialogService), i0.ɵɵdirectiveInject(i8.ProfileDialogService), i0.ɵɵdirectiveInject(i0.ViewContainerRef), i0.ɵɵdirectiveInject(i2.TitleService), i0.ɵɵdirectiveInject(i2.DeveloperModeService), i0.ɵɵdirectiveInject(i9.StartupValidationService), i0.ɵɵdirectiveInject(i10.CommandPaletteService), i0.ɵɵdirectiveInject(i2.ThemeService), i0.ɵɵdirectiveInject(i2.HomeAppPinService), i0.ɵɵdirectiveInject(i11.FileOpenService), i0.ɵɵdirectiveInject(i12.FeedbackDialogService), i0.ɵɵdirectiveInject(i12.FeedbackService), i0.ɵɵdirectiveInject(i2.ActivityService)); };
@@ -3640,16 +4037,16 @@ export class ShellComponent extends BaseAngularComponent {
3640
4037
  i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5)(_c3, 5)(_c4, 5)(TabContainerComponent, 5)(_c5, 5);
3641
4038
  } if (rf & 2) {
3642
4039
  let _t;
3643
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.searchInput = _t.first);
3644
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.mobileNavDrawer = _t.first);
3645
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.mobileNavToggle = _t.first);
3646
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.omnibarPalette = _t.first);
3647
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.shellSearchComposite = _t.first);
3648
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.tabContainerRef = _t.first);
3649
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.appAccessDialog = _t.first);
4040
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.SearchInput = _t.first);
4041
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.MobileNavDrawer = _t.first);
4042
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.MobileNavToggle = _t.first);
4043
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.OmnibarPalette = _t.first);
4044
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ShellSearchComposite = _t.first);
4045
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.TabContainerRef = _t.first);
4046
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.AppAccessDialog = _t.first);
3650
4047
  } }, hostBindings: function ShellComponent_HostBindings(rf, ctx) { if (rf & 1) {
3651
4048
  i0.ɵɵlistener("keydown", function ShellComponent_keydown_HostBindingHandler($event) { return ctx.OnGlobalKeydown($event); }, i0.ɵɵresolveDocument)("click", function ShellComponent_click_HostBindingHandler() { return ctx.onDocumentClickCloseActivity(); }, i0.ɵɵresolveDocument);
3652
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 55, vars: 35, consts: [["mobileNavToggle", ""], ["searchInput", ""], ["mobileNavDrawer", ""], ["appAccessDialog", ""], ["shellSearchComposite", ""], ["omnibarPalette", ""], [1, "shell-container"], ["href", "#mj-main-content", 1, "mj-skip-link", 3, "click"], [1, "shell-header"], ["title", "MemberJunction", 1, "mj-logo"], ["title", "Menu", "aria-label", "Menu", "aria-controls", "mj-mobile-nav-drawer", 1, "hamburger-btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-bars"], [1, "nav-bar-apps", "left-of-switcher"], [3, "activeApp", "isViewingSystemTab", "loadingAppId", "SwitcherStyle", "Provider"], [1, "desktop-nav", 3, "app", "OverflowEnabled"], [1, "desktop-nav"], [1, "spacer"], [1, "header-actions"], [1, "activity-wrap"], ["title", "Notifications", 1, "icon-btn", "desktop-only", "notification-btn"], [1, "nav-bar-apps", "left-of-user-menu"], [1, "user-menu"], [1, "avatar-btn", 3, "click"], ["alt", "", "aria-hidden", "true", 1, "avatar-img", 3, "src"], ["aria-hidden", "true", 1, "icon-fallback"], [1, "user-context-menu", 3, "menu-fade", "menu-slide"], [1, "search-popup-overlay"], [1, "search-popup"], [1, "search-popup-content", 3, "click"], ["TextField", "Name", "ValueField", "ID", "AriaLabel", "Search in", 1, "search-entity-dropdown", 3, "ngModelChange", "Data", "ngModel"], ["type", "text", "placeholder", "Search...", "aria-label", "Search", 1, "mj-input", "search-input", 3, "keydown.enter"], ["title", "Search", 1, "search-submit-btn", 3, "click"], [1, "fa-solid", "fa-search"], [1, "mobile-nav-overlay"], ["id", "mj-mobile-nav-drawer", 1, "mobile-nav-drawer"], [1, "mobile-nav-header"], ["aria-label", "Close navigation", 1, "close-btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"], [1, "mobile-nav-content"], [1, "mobile-nav-records"], [1, "mobile-nav-footer"], ["title", "Search", 1, "mobile-nav-action", 3, "click"], ["title", "Notifications", 1, "mobile-nav-action"], ["id", "mj-main-content", "role", "main", "tabindex", "-1", 3, "firstResourceLoadComplete", "layoutInitError"], [1, "shell-loading"], [3, "result"], [3, "AppSelected"], ["Placeholder", "Search everything...", 3, "IsOpen", "EnableGlobalShortcut", "ShowPromo", "PromoText"], [1, "pin-progress-backdrop"], [1, "nav-bar-app-btn", 3, "active", "title", "--app-color"], [1, "nav-bar-app-btn", 3, "click", "dblclick", "title"], [3, "appSelected", "activeApp", "isViewingSystemTab", "loadingAppId", "SwitcherStyle", "Provider"], [1, "desktop-nav", 3, "navItemClick", "navItemDismiss", "app", "OverflowEnabled"], ["title", "Search everything", 1, "search-btn"], ["title", "Search everything", 1, "search-btn", 3, "click"], [1, "fa-solid", "fa-magnifying-glass"], [1, "search-btn-text", "desktop-only"], ["aria-hidden", "true", 1, "search-btn-kbd", "desktop-only"], [1, "shell-search-bar", "desktop-only"], ["Placeholder", "Search everything...", 3, "ResultSelected", "SearchSubmitted", "SeeAllRequested", "RecentRecordSelected", "PromoAccepted", "PromoDismissed", "ShowShortcutHint", "EnablePreview", "EnableRecent", "MaxPreviewResults", "ShowPromo", "PromoText"], ["title", "Search everything", 1, "search-btn", "search-btn-mobile", 3, "click"], ["title", "Activity", 1, "icon-btn", "desktop-only", "activity-btn", 3, "click"], [1, "fa-solid", "fa-wave-square"], [1, "activity-badge"], [1, "activity-drawer"], [1, "activity-drawer", 3, "click"], [1, "activity-drawer-head"], ["title", "Clear finished", 1, "activity-clear", 3, "click"], [1, "fa-solid", "fa-broom"], [1, "activity-list"], [1, "activity-row", 3, "is-error"], [1, "activity-row"], [1, "activity-row-icon"], [1, "activity-row-body"], [1, "activity-row-label"], [1, "activity-row-detail"], [1, "activity-bar"], [1, "activity-bar-fill"], ["title", "Notifications", 1, "icon-btn", "desktop-only", "notification-btn", 3, "click"], [1, "fa-solid", "fa-bell"], [1, "notification-badge"], [1, "user-context-menu"], [1, "user-menu-header"], [1, "user-info"], [1, "user-name"], [1, "user-email"], [1, "user-subtitle"], [1, "user-menu-divider"], [1, "user-menu-item", "theme-toggle-item", 3, "title"], [1, "user-menu-item", 3, "disabled", "danger", "color", "title"], [1, "user-menu-item", "theme-toggle-item", 3, "click", "title"], [1, "menu-label"], [1, "theme-toggle-track"], [1, "theme-toggle-thumb"], [1, "theme-toggle-icon"], [1, "user-menu-item", 3, "click", "title"], [1, "menu-shortcut"], [1, "search-popup-overlay", 3, "click"], [1, "mobile-nav-overlay", 3, "click"], [1, "mobile-nav-section-title"], [3, "navItemClick", "navItemDismiss", "app", "OverflowEnabled"], [3, "Activated"], ["title", "Notifications", 1, "mobile-nav-action", 3, "click"], [1, "notification-badge-mobile"], ["size", "large", 3, "text", "textColor", "logoColor", "logoGradient", "animation"], [1, "loading-reset-panel"], [1, "loading-version"], [1, "loading-reset-message"], [1, "fa-regular", "fa-clock"], [1, "loading-reset-hint"], [1, "loading-reset-btn", 3, "click"], [1, "fa-solid", "fa-arrows-rotate"], [3, "SettingsRequested"], ["Placeholder", "Search everything...", 3, "IsOpenChange", "ResultSelected", "SeeAllRequested", "PromoAccepted", "PromoDismissed", "IsOpen", "EnableGlobalShortcut", "ShowPromo", "PromoText"], [1, "pin-progress-modal"], ["size", "small", 3, "showText"], [1, "pin-progress-text"]], template: function ShellComponent_Template(rf, ctx) { if (rf & 1) {
4049
+ } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 55, vars: 36, consts: [["mobileNavToggle", ""], ["searchInput", ""], ["mobileNavDrawer", ""], ["appAccessDialog", ""], ["shellSearchComposite", ""], ["omnibarPalette", ""], [1, "shell-container"], ["href", "#mj-main-content", 1, "mj-skip-link", 3, "click"], [1, "shell-header"], ["title", "MemberJunction", 1, "mj-logo"], ["title", "Menu", "aria-label", "Menu", "aria-controls", "mj-mobile-nav-drawer", 1, "hamburger-btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-bars"], [1, "nav-bar-apps", "left-of-switcher"], [3, "activeApp", "isViewingSystemTab", "loadingAppId", "SwitcherStyle", "Provider"], [1, "desktop-nav", 3, "app", "OverflowEnabled"], [1, "desktop-nav"], [1, "spacer"], [1, "header-actions"], [1, "activity-wrap"], ["title", "Notifications", 1, "icon-btn", "desktop-only", "notification-btn"], [1, "nav-bar-apps", "left-of-user-menu"], [1, "user-menu"], [1, "avatar-btn", 3, "click"], ["alt", "", "aria-hidden", "true", 1, "avatar-img", 3, "src"], ["aria-hidden", "true", 1, "icon-fallback"], [1, "user-context-menu", 3, "menu-fade", "menu-slide"], [1, "search-popup-overlay"], [1, "search-popup"], [1, "search-popup-content", 3, "click"], ["TextField", "Name", "ValueField", "ID", "AriaLabel", "Search in", 1, "search-entity-dropdown", 3, "ngModelChange", "Data", "ngModel"], ["type", "text", "placeholder", "Search...", "aria-label", "Search", 1, "mj-input", "search-input", 3, "keydown.enter"], ["title", "Search", 1, "search-submit-btn", 3, "click"], [1, "fa-solid", "fa-search"], [1, "mobile-nav-overlay"], ["id", "mj-mobile-nav-drawer", 1, "mobile-nav-drawer"], [1, "mobile-nav-header"], ["aria-label", "Close navigation", 1, "close-btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"], [1, "mobile-nav-content"], [1, "mobile-nav-records"], [1, "mobile-nav-footer"], ["title", "Search", 1, "mobile-nav-action", 3, "click"], ["title", "Notifications", 1, "mobile-nav-action"], ["id", "mj-main-content", "role", "main", "tabindex", "-1", 3, "firstResourceLoadComplete", "layoutInitError"], [1, "shell-loading"], [3, "result"], [3, "AppSelected", "KnowledgeSearchRequested", "ShowSearch"], ["Placeholder", "Search everything...", 3, "IsOpen", "EnableGlobalShortcut", "ShowPromo", "PromoText"], [1, "pin-progress-backdrop"], [1, "nav-bar-app-btn", 3, "active", "title", "--app-color"], [1, "nav-bar-app-btn", 3, "click", "dblclick", "title"], [3, "appSelected", "activeApp", "isViewingSystemTab", "loadingAppId", "SwitcherStyle", "Provider"], [1, "desktop-nav", 3, "navItemClick", "navItemDismiss", "app", "OverflowEnabled"], ["title", "Search everything", 1, "search-btn"], ["title", "Search everything", 1, "search-btn", 3, "click"], [1, "fa-solid", "fa-magnifying-glass"], [1, "search-btn-text", "desktop-only"], ["aria-hidden", "true", 1, "search-btn-kbd", "desktop-only"], [1, "shell-search-bar", "desktop-only"], ["Placeholder", "Search everything...", 3, "ResultSelected", "SearchSubmitted", "SeeAllRequested", "RecentRecordSelected", "PromoAccepted", "PromoDismissed", "ShowShortcutHint", "EnablePreview", "EnableRecent", "MaxPreviewResults", "ShowPromo", "PromoText"], ["title", "Search everything", 1, "search-btn", "search-btn-mobile", 3, "click"], ["title", "Activity", 1, "icon-btn", "desktop-only", "activity-btn", 3, "click"], [1, "fa-solid", "fa-wave-square"], [1, "activity-badge"], [1, "activity-drawer"], [1, "activity-drawer", 3, "click"], [1, "activity-drawer-head"], ["title", "Clear finished", 1, "activity-clear", 3, "click"], [1, "fa-solid", "fa-broom"], [1, "activity-list"], [1, "activity-row", 3, "is-error"], [1, "activity-row"], [1, "activity-row-icon"], [1, "activity-row-body"], [1, "activity-row-label"], [1, "activity-row-detail"], [1, "activity-bar"], [1, "activity-bar-fill"], ["title", "Notifications", 1, "icon-btn", "desktop-only", "notification-btn", 3, "click"], [1, "fa-solid", "fa-bell"], [1, "notification-badge"], [1, "user-context-menu"], [1, "user-menu-header"], [1, "user-info"], [1, "user-name"], [1, "user-email"], [1, "user-subtitle"], [1, "user-menu-divider"], [1, "user-menu-item", "theme-toggle-item", 3, "title"], [1, "user-menu-item", 3, "disabled", "danger", "color", "title"], [1, "user-menu-item", "theme-toggle-item", 3, "click", "title"], [1, "menu-label"], [1, "theme-toggle-track"], [1, "theme-toggle-thumb"], [1, "theme-toggle-icon"], [1, "user-menu-item", 3, "click", "title"], [1, "menu-shortcut"], [1, "search-popup-overlay", 3, "click"], [1, "mobile-nav-overlay", 3, "click"], [1, "mobile-nav-section-title"], [3, "navItemClick", "navItemDismiss", "app", "OverflowEnabled"], [3, "Activated"], ["title", "Notifications", 1, "mobile-nav-action", 3, "click"], [1, "notification-badge-mobile"], ["size", "large", 3, "text", "textColor", "logoColor", "logoGradient", "animation"], [1, "loading-reset-panel"], [1, "loading-version"], [1, "loading-reset-message"], [1, "fa-regular", "fa-clock"], [1, "loading-reset-hint"], [1, "loading-reset-btn", 3, "click"], [1, "fa-solid", "fa-arrows-rotate"], [3, "SettingsRequested"], ["Placeholder", "Search everything...", 3, "IsOpenChange", "ResultSelected", "SeeAllRequested", "PromoAccepted", "PromoDismissed", "IsOpen", "EnableGlobalShortcut", "ShowPromo", "PromoText"], [1, "pin-progress-modal"], ["size", "small", 3, "showText"], [1, "pin-progress-text"]], template: function ShellComponent_Template(rf, ctx) { if (rf & 1) {
3653
4050
  const _r1 = i0.ɵɵgetCurrentView();
3654
4051
  i0.ɵɵelementStart(0, "div", 6)(1, "a", 7);
3655
4052
  i0.ɵɵlistener("click", function ShellComponent_Template_a_click_1_listener($event) { return ctx.skipToMainContent($event); });
@@ -3715,7 +4112,7 @@ export class ShellComponent extends BaseAngularComponent {
3715
4112
  i0.ɵɵlistener("result", function ShellComponent_Template_mj_app_access_dialog_result_49_listener($event) { return ctx.onAppAccessDialogResult($event); });
3716
4113
  i0.ɵɵelementEnd();
3717
4114
  i0.ɵɵelementStart(51, "mj-command-palette", 46);
3718
- i0.ɵɵlistener("AppSelected", function ShellComponent_Template_mj_command_palette_AppSelected_51_listener($event) { return ctx.onAppSwitch($event); });
4115
+ i0.ɵɵlistener("AppSelected", function ShellComponent_Template_mj_command_palette_AppSelected_51_listener($event) { return ctx.onAppSwitch($event); })("KnowledgeSearchRequested", function ShellComponent_Template_mj_command_palette_KnowledgeSearchRequested_51_listener($event) { return ctx.OnCommandPaletteSearch($event); });
3719
4116
  i0.ɵɵelementEnd();
3720
4117
  i0.ɵɵconditionalCreate(52, ShellComponent_Conditional_52_Template, 2, 0, "mj-omnibar-palette");
3721
4118
  i0.ɵɵconditionalCreate(53, ShellComponent_Conditional_53_Template, 1, 4, "mj-search-overlay", 47);
@@ -3767,7 +4164,9 @@ export class ShellComponent extends BaseAngularComponent {
3767
4164
  i0.ɵɵclassProp("hide-tab-bar", !ctx.tabBarVisible);
3768
4165
  i0.ɵɵadvance();
3769
4166
  i0.ɵɵconditional(ctx.loading ? 48 : -1);
3770
- i0.ɵɵadvance(4);
4167
+ i0.ɵɵadvance(3);
4168
+ i0.ɵɵproperty("ShowSearch", ctx.ShowSearchBar);
4169
+ i0.ɵɵadvance();
3771
4170
  i0.ɵɵconditional(ctx.ShowSearchBar ? 52 : -1);
3772
4171
  i0.ɵɵadvance();
3773
4172
  i0.ɵɵconditional(ctx.ShowSearchBar && !ctx.UseOmnibar ? 53 : -1);
@@ -3777,31 +4176,28 @@ export class ShellComponent extends BaseAngularComponent {
3777
4176
  }
3778
4177
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShellComponent, [{
3779
4178
  type: Component,
3780
- args: [{ standalone: false, selector: 'mj-shell', template: "<div class=\"shell-container\" [class.hidden]=\"loading\" [class.tabs-visible]=\"tabBarVisible\">\n <!-- Skip link (WCAG 2.4.1) \u2014 MUST stay the first focusable element in the shell.\n Consuming apps that added their own skip link should remove it on upgrade. -->\n <a class=\"mj-skip-link\" href=\"#mj-main-content\" (click)=\"skipToMainContent($event)\">Skip to main content</a>\n\n <!-- Header -->\n <header class=\"shell-header\">\n <!-- MJ Logo -->\n <div class=\"mj-logo\" title=\"MemberJunction\"></div>\n\n <!-- Mobile Hamburger Button -->\n <button class=\"hamburger-btn\" #mobileNavToggle (click)=\"toggleMobileNav()\" title=\"Menu\"\n aria-label=\"Menu\" [attr.aria-expanded]=\"mobileNavOpen\" aria-controls=\"mj-mobile-nav-drawer\">\n <i class=\"fa-solid fa-bars\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- Nav Bar Apps: Left of App Switcher -->\n @if (leftOfSwitcherApps.length > 0 && !appSwitchingLocked) {\n <div class=\"nav-bar-apps left-of-switcher\">\n @for (app of leftOfSwitcherApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <!-- App Switcher (hidden for app-locked sessions, e.g. magic-link, and\n removable per deployment via 'Shell.AppSwitcher.Enabled') -->\n @if (!appSwitchingLocked && ShowAppSwitcher) {\n <mj-app-switcher\n [activeApp]=\"activeApp\"\n [isViewingSystemTab]=\"isViewingSystemTab\"\n [loadingAppId]=\"loadingAppId\"\n [SwitcherStyle]=\"AppSwitcherStyle\"\n [Provider]=\"Provider\"\n (appSelected)=\"onAppSwitch($event)\">\n </mj-app-switcher>\n }\n\n <!-- App Navigation (desktop only; removable per deployment via 'Shell.AppNav.Enabled') -->\n @if (activeApp && ShowAppNav) {\n <mj-app-nav\n class=\"desktop-nav\"\n [app]=\"activeApp\"\n [OverflowEnabled]=\"true\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n <!-- Records style: persistent Records pill \u2014 projected into the nav\n row's trailing slot so it sits pipe-separated right after the\n app's own items, styled as a nav pill. Appears only while any\n record is open; resumes the last-viewed record on click. -->\n @if (RecordTabsStyle) {\n <mj-records-hub-pill></mj-records-hub-pill>\n }\n </mj-app-nav>\n } @else if (RecordTabsStyle) {\n <!-- Reachability: when the app nav doesn't render (AppNav disabled per\n deployment, or no active app), open records must STILL have a way\n back \u2014 render the pill standalone in the header. -->\n <mj-records-hub-pill class=\"desktop-nav\"></mj-records-hub-pill>\n }\n\n <!-- Spacer -->\n <div class=\"spacer\"></div>\n <!-- Actions (activity, notifications, user menu) -->\n <div class=\"header-actions\">\n <!-- Universal Search \u2014 right-anchored with the action cluster, static width\n (2026-07 review consensus: visible when space permits, never resizes).\n Omnibar ON: a button styled as an input \u2014 clicking (or Ctrl/Cmd+K) opens\n the modal palette; typing happens there. Collapses to the icon circle at\n the mobile breakpoint.\n Omnibar OFF: a REAL inline input (mj-search-composite) \u2014 results attach\n beneath it as a suggest dropdown with a \"See all results\" footer. On\n mobile the composite hides and an icon button opens the Spotlight\n overlay instead (same slot, same collapse point). -->\n @if (ShowSearchBar) {\n @if (UseOmnibar) {\n <button class=\"search-btn\" title=\"Search everything\"\n [attr.aria-label]=\"'Search everything (' + OmnibarShortcutLabel + ')'\"\n (click)=\"OnHeaderSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span class=\"search-btn-text desktop-only\">Search everything</span>\n <kbd class=\"search-btn-kbd desktop-only\" aria-hidden=\"true\">{{ OmnibarShortcutLabel }}</kbd>\n </button>\n } @else {\n <div class=\"shell-search-bar desktop-only\">\n <mj-search-composite\n #shellSearchComposite\n Placeholder=\"Search everything...\"\n [ShowShortcutHint]=\"true\"\n [EnablePreview]=\"ShowSearchPreview\"\n [EnableRecent]=\"true\"\n [MaxPreviewResults]=\"8\"\n [ShowPromo]=\"ShowOmnibarPromo\"\n [PromoText]=\"OmnibarPromoText\"\n (ResultSelected)=\"OnSearchResultSelected($event)\"\n (SearchSubmitted)=\"OnSearchSubmitted($event)\"\n (SeeAllRequested)=\"OnSeeAllSearch($event)\"\n (RecentRecordSelected)=\"OnSearchResultSelected($event)\"\n (PromoAccepted)=\"OnOmnibarPromoAccepted($event)\"\n (PromoDismissed)=\"OnOmnibarPromoDismissed()\">\n </mj-search-composite>\n </div>\n <button class=\"search-btn search-btn-mobile\" title=\"Search everything\"\n [attr.aria-label]=\"'Search everything (' + OmnibarShortcutLabel + ')'\"\n (click)=\"OnHeaderSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n </button>\n }\n }\n <!-- Global Activity indicator -->\n @if (activityItems.length > 0) {\n <div class=\"activity-wrap\">\n <button class=\"icon-btn desktop-only activity-btn\" title=\"Activity\" (click)=\"toggleActivity($event)\">\n <i class=\"fa-solid fa-wave-square\" [class.activity-pulse]=\"activityRunningCount > 0\"></i>\n @if (activityRunningCount > 0) {\n <span class=\"activity-badge\">{{ activityRunningCount }}</span>\n }\n </button>\n @if (activityOpen) {\n <div class=\"activity-drawer\" (click)=\"$event.stopPropagation()\">\n <div class=\"activity-drawer-head\">\n <span>Activity</span>\n <button class=\"activity-clear\" (click)=\"clearFinishedActivity()\" title=\"Clear finished\">\n <i class=\"fa-solid fa-broom\"></i>\n </button>\n </div>\n <div class=\"activity-list\">\n @for (a of activityItems; track a.ID) {\n <div class=\"activity-row\" [class.is-error]=\"a.Status === 'error'\">\n <i class=\"activity-row-icon\"\n [class]=\"a.Status === 'running' ? 'fa-solid fa-spinner fa-spin'\n : a.Status === 'error' ? 'fa-solid fa-circle-exclamation'\n : 'fa-solid fa-circle-check'\"></i>\n <div class=\"activity-row-body\">\n <div class=\"activity-row-label\">{{ a.Label }}</div>\n @if (a.Detail) { <div class=\"activity-row-detail\">{{ a.Detail }}</div> }\n @if (a.Status === 'running' && a.Progress != null) {\n <div class=\"activity-bar\"><div class=\"activity-bar-fill\" [style.width.%]=\"a.Progress\"></div></div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n @if (ShowNotifications) {\n <button class=\"icon-btn desktop-only notification-btn\" title=\"Notifications\" (click)=\"showNotifications()\">\n <i class=\"fa-solid fa-bell\"></i>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n }\n <!-- Nav Bar Apps: Left of User Menu -->\n @if (leftOfUserMenuApps.length > 0) {\n <div class=\"nav-bar-apps left-of-user-menu\">\n @for (app of leftOfUserMenuApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <div class=\"user-menu\">\n <button class=\"avatar-btn\" (click)=\"toggleUserMenu($event)\"\n [attr.aria-label]=\"avatarAriaLabel\"\n [attr.aria-expanded]=\"userMenuVisible\">\n @if (userImageURL) {\n <img [src]=\"userImageURL\" alt=\"\" aria-hidden=\"true\" class=\"avatar-img\" />\n } @else {\n <div class=\"icon-fallback\" aria-hidden=\"true\">\n <i [class]=\"userIconClass || 'fa-solid fa-user'\"></i>\n </div>\n }\n </button>\n <!-- User Context Menu (Dynamic) -->\n @if (userMenuVisible) {\n <div class=\"user-context-menu\"\n [class.menu-fade]=\"getUserMenuOptions()?.animationStyle === 'fade'\"\n [class.menu-slide]=\"getUserMenuOptions()?.animationStyle === 'slide'\">\n <!-- User Header -->\n @if (getUserMenuOptions()?.showUserName) {\n <div class=\"user-menu-header\">\n <div class=\"user-info\">\n <span class=\"user-name\">{{ getUserDisplayInfo()?.name || userName }}</span>\n @if (getUserMenuOptions()?.showUserEmail && getUserDisplayInfo()?.email) {\n <span class=\"user-email\">\n {{ getUserDisplayInfo()?.email }}\n </span>\n }\n @if (getUserDisplayInfo()?.subtitle) {\n <span class=\"user-subtitle\">{{ getUserDisplayInfo()?.subtitle }}</span>\n }\n </div>\n </div>\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Dynamic Menu Items -->\n @for (element of userMenuElements; track element) {\n <!-- Divider -->\n @if (isMenuDivider(element)) {\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Menu Item -->\n @if (!isMenuDivider(element)) {\n @if (asMenuItem(element); as item) {\n @if (item.id === 'toggle-theme') {\n <div class=\"user-menu-item theme-toggle-item\"\n [title]=\"item.tooltip || ''\"\n (click)=\"onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n <div class=\"theme-toggle-track\" [class.dark]=\"IsDarkMode\">\n <div class=\"theme-toggle-thumb\">\n <i [class]=\"IsDarkMode ? 'fa-solid fa-moon' : 'fa-solid fa-sun'\" class=\"theme-toggle-icon\"></i>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"user-menu-item\"\n [class.disabled]=\"!item.enabled\"\n [class.danger]=\"item.cssClass === 'danger'\"\n [style.color]=\"item.color || null\"\n [title]=\"item.tooltip || ''\"\n (click)=\"item.enabled && onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"menu-shortcut\">{{ item.shortcut }}</span>\n }\n </div>\n }\n }\n }\n }\n </div>\n }\n </div>\n </div>\n </header>\n\n <!-- Search Popup -->\n @if (isSearchOpen) {\n <div class=\"search-popup-overlay\" (click)=\"closeSearch()\"></div>\n }\n <div class=\"search-popup\" [class.open]=\"isSearchOpen\" [attr.inert]=\"isSearchOpen ? null : ''\">\n <div class=\"search-popup-content\" (click)=\"$event.stopPropagation()\">\n <mj-dropdown\n [Data]=\"searchableEntities\"\n TextField=\"Name\"\n ValueField=\"ID\"\n AriaLabel=\"Search in\"\n class=\"search-entity-dropdown\"\n [(ngModel)]=\"selectedEntity\">\n </mj-dropdown>\n <input\n type=\"text\"\n #searchInput\n placeholder=\"Search...\"\n aria-label=\"Search\"\n class=\"mj-input search-input\"\n (keydown.enter)=\"onSearch($event)\"\n />\n <button class=\"search-submit-btn\" (click)=\"onSearch($event)\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n </div>\n </div>\n\n <!-- Mobile Navigation Drawer -->\n @if (mobileNavOpen) {\n <div class=\"mobile-nav-overlay\" (click)=\"closeMobileNav()\"></div>\n }\n <div class=\"mobile-nav-drawer\" id=\"mj-mobile-nav-drawer\" #mobileNavDrawer\n [class.open]=\"mobileNavOpen\" [attr.inert]=\"mobileNavOpen ? null : ''\">\n <div class=\"mobile-nav-header\">\n <span>Navigation</span>\n <button class=\"close-btn\" (click)=\"closeMobileNav()\" aria-label=\"Close navigation\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n @if (activeApp && ShowAppNav) {\n <div class=\"mobile-nav-content\">\n <div class=\"mobile-nav-section-title\">{{ activeApp.Name }}</div>\n <mj-app-nav\n [app]=\"activeApp\"\n [OverflowEnabled]=\"false\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n </div>\n }\n <!-- Records style: the desktop pill lives in the (mobile-hidden) app nav,\n so the drawer carries its own entry \u2014 without it, open records are\n unreachable on mobile except by re-opening them. Resumes the\n last-viewed record and closes the drawer. -->\n @if (RecordTabsStyle) {\n <div class=\"mobile-nav-records\">\n <mj-records-hub-pill (Activated)=\"closeMobileNav()\"></mj-records-hub-pill>\n </div>\n }\n <div class=\"mobile-nav-footer\">\n <button class=\"mobile-nav-action\" title=\"Search\" (click)=\"toggleSearch(); closeMobileNav()\">\n <i class=\"fa-solid fa-search\"></i>\n <span>Search</span>\n </button>\n @if (ShowNotifications) {\n <button class=\"mobile-nav-action\" title=\"Notifications\" (click)=\"showNotifications(); closeMobileNav()\">\n <i class=\"fa-solid fa-bell\"></i>\n <span>Notifications</span>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge-mobile\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Tab Container - with dynamic tab bar visibility -->\n <mj-tab-container\n id=\"mj-main-content\"\n role=\"main\"\n tabindex=\"-1\"\n [class.hide-tab-bar]=\"!tabBarVisible\"\n (firstResourceLoadComplete)=\"onFirstResourceLoadComplete()\"\n (layoutInitError)=\"handleLayoutError()\">\n </mj-tab-container>\n</div>\n\n<!-- Loading State -->\n@if (loading) {\n <div class=\"shell-loading\">\n <mj-loading\n [text]=\"currentLoadingText\"\n [textColor]=\"currentLoadingTextColor\"\n [logoColor]=\"currentLoadingColor\"\n [logoGradient]=\"currentLoadingGradient\"\n [animation]=\"currentLoadingAnimation\"\n size=\"large\">\n </mj-loading>\n @if (ShowResetOption) {\n <div class=\"loading-reset-panel\">\n <p class=\"loading-reset-message\">\n <i class=\"fa-regular fa-clock\"></i>\n Taking longer than expected\n </p>\n <p class=\"loading-reset-hint\">\n This can happen after updates or due to cached data issues.\n </p>\n <button class=\"loading-reset-btn\" (click)=\"ResetApplication()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i>\n Reset\n </button>\n </div>\n } @else {\n <div class=\"loading-version\">MemberJunction \u00B7 v{{ MJVersion }}</div>\n }\n </div>\n}\n\n<!-- App Access Error Dialog -->\n<mj-app-access-dialog\n #appAccessDialog\n (result)=\"onAppAccessDialogResult($event)\">\n</mj-app-access-dialog>\n\n<!-- Command Palette -->\n<mj-command-palette (AppSelected)=\"onAppSwitch($event)\"></mj-command-palette>\n\n<!-- Unified command palette (Shell.Omnibar.Enabled). Rendered only while the search bar is:\n when Instance Config or the host's chrome policy hides search, the palette must not\n remain reachable through Ctrl/Cmd+K. -->\n@if (ShowSearchBar) {\n <mj-omnibar-palette #omnibarPalette (SettingsRequested)=\"OnPaletteSettingsRequested()\"></mj-omnibar-palette>\n}\n\n<!-- Legacy universal search as a Spotlight-style overlay \u2014 omnibar-off MOBILE only\n (on desktop the inline header composite owns the experience; this opens from\n the mobile icon or Ctrl/Cmd+K when the composite isn't visible).\n The shell owns the Ctrl/Cmd+K chord, so the overlay's own global shortcut is off. -->\n@if (ShowSearchBar && !UseOmnibar) {\n <mj-search-overlay\n [(IsOpen)]=\"LegacySearchOpen\"\n Placeholder=\"Search everything...\"\n [EnableGlobalShortcut]=\"false\"\n [ShowPromo]=\"ShowOmnibarPromo\"\n [PromoText]=\"OmnibarPromoText\"\n (ResultSelected)=\"OnOverlayResultSelected($event)\"\n (SeeAllRequested)=\"OnOverlaySeeAll($event)\"\n (PromoAccepted)=\"OnOmnibarPromoAccepted($event)\"\n (PromoDismissed)=\"OnOmnibarPromoDismissed()\">\n </mj-search-overlay>\n}\n\n<!-- Pin Progress Overlay -->\n@if (PinProgressVisible) {\n <div class=\"pin-progress-backdrop\">\n <div class=\"pin-progress-modal\">\n <mj-loading [showText]=\"false\" size=\"small\"></mj-loading>\n <span class=\"pin-progress-text\">{{ PinProgressText }}</span>\n </div>\n </div>\n}\n", styles: [":host {\n display: block;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* MJ Logo - uses design tokens for theme switching & white-labeling.\n Geometry follows --mj-logo-width/-height so a differently-shaped org mark fits;\n --mj-logo-plate-bg is set by the theme engine only when a single-variant logo needs\n a legibility plate in the mode it wasn't designed for (org-theming Phase 4). */\n.mj-logo {\n width: var(--mj-logo-width, 32px);\n height: var(--mj-logo-height, 18px);\n background-image: var(--mj-logo-mark);\n background-color: var(--mj-logo-plate-bg, transparent);\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n border-radius: var(--mj-radius-sm, 4px);\n flex-shrink: 0;\n}\n\n/* Skip link (WCAG 2.4.1) \u2014 off-screen until focused, then pinned over the header.\n It is the first focusable element in the shell; consuming apps should drop their\n own skip link on upgrade rather than stacking a second one in front of this. */\n.mj-skip-link {\n position: absolute;\n top: var(--mj-space-2);\n left: var(--mj-space-2);\n z-index: 100000;\n padding: var(--mj-space-2) var(--mj-space-4);\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n text-decoration: none;\n transform: translateY(-200%);\n transition: transform var(--mj-transition-fast);\n}\n.mj-skip-link:focus,\n.mj-skip-link:focus-visible {\n transform: translateY(0);\n outline: var(--mj-ring-width) solid var(--mj-focus-ring-color);\n outline-offset: var(--mj-ring-offset);\n}\n\n/* The main landmark is also the skip-link target; focusing it programmatically must\n not paint a ring on a region the user did not tab to. */\nmj-tab-container:focus {\n outline: none;\n}\n\n.shell-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n position: relative;\n}\n\n/* Hide shell container while loading - allows tab container to render and load\n first resource in background while shell loading indicator is visible */\n.shell-container.hidden {\n visibility: hidden;\n position: absolute;\n pointer-events: none;\n}\n\nmj-tab-container {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.2s ease-in-out;\n}\n\n/* Hide Golden Layout tab headers when only one tab \u2014 scoped to the MAIN\n region: the RECORDS region is a separate Golden Layout whose tab strip is\n its whole point (switching between open records) and must never be hidden\n by the main bar's visibility policy. */\nmj-tab-container.hide-tab-bar ::ng-deep .main-region .lm_header {\n display: none;\n}\n\n/* Show tab headers when multiple tabs */\nmj-tab-container:not(.hide-tab-bar) ::ng-deep .main-region .lm_header {\n opacity: 1;\n max-height: 40px;\n transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out;\n}\n\n/* Adjust content area height when tabs hidden */\nmj-tab-container.hide-tab-bar ::ng-deep .main-region .lm_content {\n height: 100% !important;\n}\n\n/* Ensure smooth transitions */\nmj-tab-container ::ng-deep .lm_stack {\n transition: all 0.2s ease-in-out;\n}\n\n.shell-header {\n height: 60px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 var(--mj-space-4);\n gap: var(--mj-space-4);\n box-shadow: var(--mj-shadow-sm);\n flex-shrink: 0;\n /* Creates a stacking context so children (search bar z-index: 99999, dropdown)\n are contained within this level. Modal overlays (z-index: 1000+) sit above. */\n position: relative;\n z-index: 500;\n}\n\n/* Nav Bar Apps - permanent app icons in the header */\n.nav-bar-apps {\n display: flex;\n align-items: center;\n gap: var(--mj-space-1);\n}\n\n.nav-bar-apps.left-of-switcher {\n /* No extra margin - header gap handles spacing */\n /* This prevents the app switcher from shifting when icons are hidden */\n}\n\n.nav-bar-apps.left-of-user-menu {\n margin-right: var(--mj-space-2);\n}\n\n.nav-bar-app-btn {\n --app-color: var(--mj-text-muted);\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-lg);\n transition: all var(--mj-transition-fast);\n position: relative;\n}\n\n.nav-bar-app-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Active = brand-primary (matches nav pills + switcher items); the app's\n metadata color is reserved for identity accents, never state.\n Shared active-tint recipe. */\n.nav-bar-app-btn.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n\n.nav-bar-app-btn i {\n transition: transform var(--mj-transition-fast);\n margin-top: var(--mj-space-0-5); /* Align with app switcher icon */\n}\n\n.nav-bar-app-btn:hover i {\n transform: scale(1.1);\n}\n\n.spacer {\n flex: 1;\n}\n\n/* Priority+ overflow support: on desktop the app nav owns the header's free\n middle space (so it knows exactly how much room its items have) and the\n spacer stands down. On mobile the nav is display:none'd, so the spacer's\n flex: 1 keeps the action cluster right-aligned. When the nav isn't rendered\n at all (app-locked sessions, Shell.AppNav.Enabled off), the sibling selector\n doesn't match and the spacer behaves as before. */\n@media (min-width: 769px) {\n mj-app-nav.desktop-nav {\n flex: 1 1 auto;\n min-width: 0;\n }\n mj-app-nav.desktop-nav + .spacer {\n flex: 0 0 0px;\n }\n}\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-lg);\n transition: background var(--mj-transition-fast);\n}\n\n.icon-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Keyboard focus parity for all header controls (the search bar already has\n its own ring at .search-btn:focus-visible). */\n.nav-bar-app-btn:focus-visible,\n.icon-btn:focus-visible,\n.hamburger-btn:focus-visible,\n.user-menu .avatar-btn:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n/* Universal search: a static-width bar styled as an input, anchored on the\n right with the action cluster. Deliberately NOT resizable on hover/focus \u2014\n chrome that shifts on interaction was rejected in the 2026-07 review. It's a\n button (typing happens in the overlay it opens), so no focus/caret styling. */\n.search-btn {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n width: 260px;\n height: 36px;\n padding: 0 var(--mj-space-2-5) 0 var(--mj-space-3-5);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n font-family: inherit;\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.search-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n}\n\n.search-btn:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n/* Omnibar-off mobile trigger: hidden on desktop (the inline composite renders\n instead); the 768px block flips it on as the icon circle. */\n.search-btn-mobile {\n display: none;\n}\n\n/* Omnibar-off desktop: the inline search composite, same slot + width as the\n omnibar bar so the opt-in toggle doesn't move the header. */\n.shell-search-bar {\n position: relative;\n width: 260px;\n z-index: 99999;\n}\n\n/* The suggest dropdown needs more width than its 260px anchor. Pin it to the\n anchor's right edge and let it grow leftward \u2014 the bar is right-anchored, so\n leftward growth stays on screen. */\n.shell-search-bar ::ng-deep .suggest-dropdown {\n left: auto;\n right: 0;\n width: min(560px, calc(100vw - var(--mj-space-6)));\n}\n\n.search-btn i {\n font-size: var(--mj-text-sm);\n flex-shrink: 0;\n}\n\n.search-btn .search-btn-text {\n flex: 1;\n min-width: 0;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* kbd chip: deliberately mirrors the omnibar palette's .ob-kbd chrome\n (10.5px / 5px radius / bottom-heavy border) so the affordance and the\n surface it summons read as one system. */\n.search-btn .search-btn-kbd {\n flex-shrink: 0;\n white-space: nowrap;\n font-family: inherit;\n font-size: 10.5px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-bottom-width: 2px;\n border-radius: 5px;\n padding: 1.5px var(--mj-space-1-5);\n}\n\n/* Notification button with badge */\n.notification-btn {\n position: relative;\n}\n\n.notification-badge {\n position: absolute;\n top: 4px;\n right: 4px;\n min-width: 16px;\n height: 16px;\n padding: 0 var(--mj-space-1);\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: var(--mj-leading-none);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.user-menu {\n position: relative;\n}\n\n.user-menu .avatar-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: box-shadow var(--mj-transition-fast), transform var(--mj-transition-fast);\n padding: 0;\n overflow: hidden;\n}\n\n.user-menu .avatar-btn:hover {\n transform: scale(1.05);\n}\n\n/* Icon fallback styling - shows gray circle with icon */\n.user-menu .avatar-btn .icon-fallback {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-surface-hover);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: border-color var(--mj-transition-fast);\n}\n\n.user-menu .avatar-btn:hover .icon-fallback {\n border-color: var(--mj-brand-primary);\n}\n\n.user-menu .avatar-btn .icon-fallback i {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n}\n\n/* Avatar image - replaces the gray circle entirely */\n.user-menu .avatar-btn .avatar-img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n box-shadow: var(--mj-shadow-sm);\n}\n\n.user-menu .avatar-btn:hover .avatar-img {\n box-shadow: var(--mj-shadow-md);\n}\n\n/* User Menu Header */\n.user-menu-header {\n padding: var(--mj-space-3) var(--mj-space-4);\n background: var(--mj-bg-surface-sunken);\n}\n\n.user-menu-header .user-info {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-0-5);\n}\n\n.user-menu-header .user-name {\n font-weight: var(--mj-font-semibold);\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.user-menu-header .user-email {\n font-size: var(--mj-text-xs);\n color: var(--mj-text-muted);\n}\n\n.user-menu-header .user-subtitle {\n font-size: 11px;\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n/* User Context Menu */\n.user-context-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: var(--mj-space-2);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 180px;\n /* Never wider than the viewport, never taller than what is left below the 60px header:\n a short window (or a phone) used to leave the bottom items unreachable, because the\n menu had no height limit and clipped instead of scrolling. */\n max-width: min(360px, calc(100vw - 2 * var(--mj-space-4)));\n max-height: calc(100vh - 60px - 3 * var(--mj-space-2));\n max-height: calc(100dvh - 60px - 3 * var(--mj-space-2));\n z-index: 10000;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.user-menu-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n transition: background var(--mj-transition-fast);\n}\n\n.user-menu-item i {\n width: 18px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n}\n\n.user-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.user-menu-item.danger {\n color: var(--mj-status-error);\n}\n\n.user-menu-item.danger i {\n color: var(--mj-status-error);\n}\n\n.user-menu-item.danger:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface-elevated));\n}\n\n.user-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: var(--mj-space-1) 0;\n}\n\n/* Menu item disabled state */\n.user-menu-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.user-menu-item.disabled:hover {\n background: transparent;\n}\n\n/* Theme Toggle Switch */\n.theme-toggle-item {\n cursor: pointer;\n}\n\n.theme-toggle-track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: var(--mj-radius-full);\n background: var(--mj-bg-surface-active);\n margin-left: auto;\n transition: background var(--mj-transition-base);\n flex-shrink: 0;\n}\n\n.theme-toggle-track.dark {\n background: var(--mj-brand-primary);\n}\n\n.theme-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform var(--mj-transition-base);\n}\n\n.theme-toggle-track.dark .theme-toggle-thumb {\n transform: translateX(18px);\n}\n\n.theme-toggle-icon {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.theme-toggle-track.dark .theme-toggle-icon {\n color: var(--mj-text-inverse);\n}\n\n/* Menu shortcut hint */\n.user-menu-item .menu-shortcut {\n margin-left: auto;\n font-size: 11px;\n color: var(--mj-text-disabled);\n font-family: monospace;\n}\n\n/* Menu animations */\n.user-context-menu.menu-fade {\n animation: menuFadeIn 0.15s ease-out;\n}\n\n.user-context-menu.menu-slide {\n animation: menuSlideIn 0.2s ease-out;\n}\n\n@keyframes menuFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes menuSlideIn {\n from {\n opacity: 0;\n transform: translateY(-16px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.shell-loading {\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n z-index: 99999;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-bottom: 15vh;\n background: var(--mj-bg-page);\n}\n\n.loading-version {\n margin-top: var(--mj-space-8, 24px);\n font-size: 11px;\n color: var(--mj-text-muted);\n letter-spacing: 0.4px;\n opacity: 0.6;\n user-select: none;\n}\n\n/* Loading Recovery Reset Panel */\n.loading-reset-panel {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--mj-space-3);\n margin-top: var(--mj-space-10);\n padding: var(--mj-space-8);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-md);\n max-width: 420px;\n animation: resetPanelFadeIn 0.4s ease-out;\n}\n\n.loading-reset-message {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n\n.loading-reset-message i {\n font-size: var(--mj-text-lg);\n color: var(--mj-status-warning);\n}\n\n.loading-reset-hint {\n margin: 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n text-align: center;\n line-height: var(--mj-leading-relaxed);\n}\n\n.loading-reset-btn {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-6);\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-medium);\n font-family: var(--mj-font-family);\n color: var(--mj-text-inverse);\n background: var(--mj-brand-primary);\n border: 1px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.loading-reset-btn:hover {\n background: var(--mj-brand-primary-hover);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: var(--mj-shadow-md);\n}\n\n.loading-reset-btn:active {\n background: var(--mj-brand-primary-active);\n color: var(--mj-text-inverse);\n transform: scale(0.98);\n}\n\n.loading-reset-btn i {\n font-size: var(--mj-text-sm);\n}\n\n@keyframes resetPanelFadeIn {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Hamburger button - hidden on desktop */\n.hamburger-btn {\n display: none;\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: none;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-xl);\n transition: background var(--mj-transition-fast);\n}\n.hamburger-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile Navigation Overlay */\n.mobile-nav-overlay {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 9998;\n}\n\n/* Mobile Navigation Drawer */\n/* `visibility` is transitioned alongside `transform` so the closed drawer is genuinely\n hidden \u2014 off-screen alone leaves its controls hit-testable and visible to\n checkVisibility(), which is what let 6 of its 8 controls keep taking focus. The\n delay holds visibility through the slide-out, then drops it; opening is instant\n (0s) so the drawer is present for the whole slide-in. `inert` (set in the template)\n is the actual focus guard; this keeps the CSS from disagreeing with it. */\n.mobile-nav-drawer {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 85vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-xl);\n z-index: 9999;\n flex-direction: column;\n transform: translateX(-100%);\n visibility: hidden;\n transition: transform var(--mj-transition-slow), visibility 0s linear 300ms;\n}\n.mobile-nav-drawer.open {\n transform: translateX(0);\n visibility: visible;\n transition: transform var(--mj-transition-slow), visibility 0s linear 0s;\n}\n\n/* Reduced motion: no slide, so nothing to hold visibility open for. */\n@media (prefers-reduced-motion: reduce) {\n .mobile-nav-drawer,\n .mobile-nav-drawer.open {\n transition: none;\n }\n}\n\n.mobile-nav-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-4) var(--mj-space-5);\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n.mobile-nav-header span {\n font-weight: var(--mj-font-semibold);\n font-size: var(--mj-text-base);\n color: var(--mj-text-primary);\n}\n.mobile-nav-header .close-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-lg);\n transition: background var(--mj-transition-fast);\n}\n.mobile-nav-header .close-btn:hover {\n background: var(--mj-bg-surface-active);\n}\n\n.mobile-nav-content {\n flex: 1;\n overflow-y: auto;\n padding: var(--mj-space-4) 0;\n}\n\n.mobile-nav-section-title {\n padding: var(--mj-space-2) var(--mj-space-5);\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n/* Records entry in the mobile drawer (records-style record opens) \u2014\n the desktop pill lives in the mobile-hidden app nav, so the drawer\n carries its own touch-friendly instance. */\n.mobile-nav-records {\n padding: var(--mj-space-2) var(--mj-space-3);\n border-top: 1px solid var(--mj-border-default);\n}\n.mobile-nav-records .records-pill {\n width: 100%;\n min-height: 48px; /* Touch-target parity with drawer nav items */\n justify-content: flex-start;\n}\n\n.mobile-nav-footer {\n border-top: 1px solid var(--mj-border-default);\n padding: var(--mj-space-3) var(--mj-space-4);\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-1);\n}\n\n.mobile-nav-action {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border: none;\n background: none;\n cursor: pointer;\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n transition: background var(--mj-transition-fast);\n width: 100%;\n text-align: left;\n}\n.mobile-nav-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n.mobile-nav-action i {\n font-size: var(--mj-text-base);\n width: 20px;\n text-align: center;\n}\n\n/* Mobile Responsive Styles */\n@media (max-width: 768px) {\n .hamburger-btn {\n display: flex;\n }\n\n .desktop-nav {\n display: none !important;\n }\n\n .desktop-only {\n display: none !important;\n }\n\n .mobile-nav-overlay {\n display: block;\n }\n\n .mobile-nav-drawer {\n display: flex;\n }\n\n .shell-header {\n padding: 0 var(--mj-space-3);\n gap: var(--mj-space-2);\n }\n\n /* Keep the right-edge cluster (chat/nav-bar app icons + avatar) on one row.\n Without pinning this, the icons can wrap and the avatar drops onto a second\n line, stacking under the chat icon. */\n .header-actions {\n flex-direction: row;\n flex-wrap: nowrap;\n }\n .header-actions .user-menu,\n .header-actions .nav-bar-apps,\n .header-actions .search-btn {\n flex-shrink: 0;\n }\n\n /* Search bar collapses to the icon-only circle on small viewports \u2014\n the placeholder text + kbd chip are already hidden via .desktop-only.\n The omnibar-off mobile trigger (.search-btn-mobile) becomes visible here;\n its .desktop-only composite sibling is hidden by the rule above. */\n .search-btn {\n display: flex;\n width: 40px;\n height: 40px;\n padding: 0;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: none;\n }\n .search-btn i {\n font-size: var(--mj-text-lg);\n }\n .search-btn:hover {\n background: var(--mj-bg-surface-hover);\n border: none;\n }\n}\n\n/* Settings Full-Screen Window Styles */\n::ng-deep .settings-fullscreen-window {\n /* Remove window chrome for full-screen feel */\n box-shadow: none !important;\n border: none !important;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar {\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n padding: var(--mj-space-3) var(--mj-space-4);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-title {\n font-size: var(--mj-text-lg);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-actions {\n gap: var(--mj-space-1);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action {\n width: 32px;\n height: 32px;\n border-radius: var(--mj-radius-md);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: var(--mj-bg-page);\n}\n\n/* Hide minimize/maximize buttons - only show close */\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Minimize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Maximize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-minimize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-maximize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window {\n display: none !important;\n}\n\n/* Universal search is a right-anchored bar affordance in header-actions\n (2026-07 design review round 2): both experiences still open as overlays \u2014\n the bar is a trigger, not an inline input. Icon-only under 768px. */\n\n/* ========================================\n SEARCH POPUP\n ======================================== */\n\n.search-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 9999;\n animation: fadeIn 0.15s ease;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.search-popup {\n position: absolute;\n top: 60px; /* Below header */\n right: 16px;\n width: 480px;\n max-width: calc(100vw - 32px);\n background: var(--mj-bg-surface-elevated);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-xl);\n z-index: 10000;\n opacity: 0;\n transform: translateY(-10px);\n pointer-events: none;\n visibility: hidden;\n transition: opacity var(--mj-transition-base), transform var(--mj-transition-base), visibility 0s linear 200ms;\n}\n\n.search-popup.open {\n opacity: 1;\n transform: translateY(0);\n pointer-events: all;\n visibility: visible;\n transition: opacity var(--mj-transition-base), transform var(--mj-transition-base), visibility 0s linear 0s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .search-popup,\n .search-popup.open {\n transition: none;\n }\n}\n\n.search-popup-content {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-3);\n}\n\n.search-entity-dropdown {\n min-width: 140px;\n max-width: 180px;\n flex-shrink: 0;\n}\n\n.search-input {\n flex: 1;\n min-width: 0;\n height: 40px;\n padding: var(--mj-space-2) var(--mj-space-3);\n font-size: var(--mj-text-sm);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n outline: none;\n display: block !important;\n box-sizing: border-box;\n}\n\n.search-input:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.search-submit-btn {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--mj-text-base);\n transition: background var(--mj-transition-fast);\n flex-shrink: 0;\n}\n\n.search-submit-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.search-submit-btn:active {\n background: var(--mj-brand-primary-hover);\n}\n\n/* Mobile notification badge */\n.notification-badge-mobile {\n position: absolute;\n top: 8px;\n right: 8px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: var(--mj-leading-none);\n box-shadow: var(--mj-shadow-sm);\n}\n\n/* Mobile-specific search popup adjustments */\n@media (max-width: 768px) {\n .search-popup {\n top: 60px;\n left: 16px;\n right: 16px;\n width: auto;\n max-width: none;\n }\n\n .search-popup-content {\n flex-wrap: wrap;\n }\n\n .search-entity-dropdown {\n width: 100%;\n }\n\n .search-input {\n flex: 1;\n min-width: 0;\n }\n}\n\n/* ========================================\n PIN PROGRESS OVERLAY\n ======================================== */\n.pin-progress-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: color-mix(in srgb, var(--mj-text-primary) 40%, transparent);\n z-index: 100000;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: pinBackdropIn 0.2s ease;\n}\n\n@keyframes pinBackdropIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.pin-progress-modal {\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-xl);\n padding: var(--mj-space-7) var(--mj-space-10);\n display: flex;\n align-items: center;\n gap: var(--mj-space-4);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--mj-text-primary) 30%, transparent);\n animation: pinModalIn 0.25s ease;\n}\n\n@keyframes pinModalIn {\n from { opacity: 0; transform: scale(0.9) translateY(8px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n}\n\n.pin-progress-text {\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n/* ============================================================\n Global Activity indicator (P3)\n ============================================================ */\n.activity-wrap { position: relative; display: inline-flex; }\n.activity-btn { position: relative; }\n.activity-btn .activity-pulse { color: var(--mj-brand-primary); }\n.activity-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 var(--mj-space-1);\n border-radius: var(--mj-radius-md);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n line-height: 16px;\n text-align: center;\n}\n.activity-drawer {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 320px;\n max-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: 0 8px 28px color-mix(in srgb, var(--mj-text-primary) 18%, transparent);\n z-index: 1000;\n overflow: hidden;\n}\n.activity-drawer-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-2-5) var(--mj-space-3);\n font-weight: var(--mj-font-bold);\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-default);\n}\n.activity-clear {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.8rem;\n padding: var(--mj-space-1) var(--mj-space-1-5);\n border-radius: var(--mj-radius-md);\n}\n.activity-clear:hover { color: var(--mj-text-primary); background: var(--mj-bg-surface-hover); }\n.activity-list { overflow-y: auto; padding: var(--mj-space-1-5); }\n.activity-row {\n display: flex;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2);\n border-radius: var(--mj-radius-md);\n}\n.activity-row:hover { background: var(--mj-bg-surface-hover); }\n.activity-row-icon { margin-top: var(--mj-space-0-5); color: var(--mj-brand-primary); font-size: 0.85rem; }\n.activity-row.is-error .activity-row-icon { color: var(--mj-status-error); }\n.activity-row-body { flex: 1; min-width: 0; }\n.activity-row-label { font-size: 0.82rem; font-weight: var(--mj-font-semibold); color: var(--mj-text-primary); }\n.activity-row-detail { font-size: 0.72rem; color: var(--mj-text-muted); margin-top: var(--mj-space-px); }\n.activity-bar {\n margin-top: var(--mj-space-1-5);\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n}\n.activity-bar-fill { height: 100%; background: var(--mj-brand-primary); transition: width var(--mj-transition-slow); }\n\n"] }]
3781
- }], () => [{ type: i1.ApplicationManager }, { type: i1.WorkspaceStateManager }, { type: i1.GoldenLayoutManager }, { type: i1.TabService }, { type: i2.NavigationService }, { type: i3.ActivatedRoute }, { type: i3.Router }, { type: i4.MJAuthBase }, { type: i0.ChangeDetectorRef }, { type: i5.UserAvatarService }, { type: i6.UserSharingCenterDialogService }, { type: i7.AboutDialogService }, { type: i8.ProfileDialogService }, { type: i0.ViewContainerRef }, { type: i2.TitleService }, { type: i2.DeveloperModeService }, { type: i9.StartupValidationService }, { type: i10.CommandPaletteService }, { type: i2.ThemeService }, { type: i2.HomeAppPinService }, { type: i11.FileOpenService }, { type: i12.FeedbackDialogService }, { type: i12.FeedbackService }, { type: i2.ActivityService }], { searchInput: [{
4179
+ args: [{ standalone: false, selector: 'mj-shell', template: "<div class=\"shell-container\" [class.hidden]=\"loading\" [class.tabs-visible]=\"tabBarVisible\">\n <!-- Skip link (WCAG 2.4.1) \u2014 MUST stay the first focusable element in the shell.\n Consuming apps that added their own skip link should remove it on upgrade. -->\n <a class=\"mj-skip-link\" href=\"#mj-main-content\" (click)=\"skipToMainContent($event)\">Skip to main content</a>\n\n <!-- Header -->\n <header class=\"shell-header\">\n <!-- MJ Logo -->\n <div class=\"mj-logo\" title=\"MemberJunction\"></div>\n\n <!-- Mobile Hamburger Button -->\n <button class=\"hamburger-btn\" #mobileNavToggle (click)=\"toggleMobileNav()\" title=\"Menu\"\n aria-label=\"Menu\" [attr.aria-expanded]=\"mobileNavOpen\" aria-controls=\"mj-mobile-nav-drawer\">\n <i class=\"fa-solid fa-bars\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- Nav Bar Apps: Left of App Switcher -->\n @if (leftOfSwitcherApps.length > 0 && !appSwitchingLocked) {\n <div class=\"nav-bar-apps left-of-switcher\">\n @for (app of leftOfSwitcherApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <!-- App Switcher (hidden for app-locked sessions, e.g. magic-link, and\n removable per deployment via 'Shell.AppSwitcher.Enabled') -->\n @if (!appSwitchingLocked && ShowAppSwitcher) {\n <mj-app-switcher\n [activeApp]=\"activeApp\"\n [isViewingSystemTab]=\"isViewingSystemTab\"\n [loadingAppId]=\"loadingAppId\"\n [SwitcherStyle]=\"AppSwitcherStyle\"\n [Provider]=\"Provider\"\n (appSelected)=\"onAppSwitch($event)\">\n </mj-app-switcher>\n }\n\n <!-- App Navigation (desktop only; removable per deployment via 'Shell.AppNav.Enabled') -->\n @if (activeApp && ShowAppNav) {\n <mj-app-nav\n class=\"desktop-nav\"\n [app]=\"activeApp\"\n [OverflowEnabled]=\"true\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n <!-- Records style: persistent Records pill \u2014 projected into the nav\n row's trailing slot so it sits pipe-separated right after the\n app's own items, styled as a nav pill. Appears only while any\n record is open; resumes the last-viewed record on click. -->\n @if (RecordTabsStyle) {\n <mj-records-hub-pill></mj-records-hub-pill>\n }\n </mj-app-nav>\n } @else if (RecordTabsStyle) {\n <!-- Reachability: when the app nav doesn't render (AppNav disabled per\n deployment, or no active app), open records must STILL have a way\n back \u2014 render the pill standalone in the header. -->\n <mj-records-hub-pill class=\"desktop-nav\"></mj-records-hub-pill>\n }\n\n <!-- Spacer -->\n <div class=\"spacer\"></div>\n <!-- Actions (activity, notifications, user menu) -->\n <div class=\"header-actions\">\n <!-- Universal Search \u2014 right-anchored with the action cluster, static width\n (2026-07 review consensus: visible when space permits, never resizes).\n Omnibar ON: a button styled as an input \u2014 clicking (or Ctrl/Cmd+K) opens\n the modal palette; typing happens there. Collapses to the icon circle at\n the mobile breakpoint.\n Omnibar OFF: a REAL inline input (mj-search-composite) \u2014 results attach\n beneath it as a suggest dropdown with a \"See all results\" footer. On\n mobile the composite hides and an icon button opens the Spotlight\n overlay instead (same slot, same collapse point). -->\n @if (ShowSearchBar) {\n @if (UseOmnibar) {\n <button class=\"search-btn\" title=\"Search everything\"\n [attr.aria-label]=\"'Search everything (' + OmnibarShortcutLabel + ')'\"\n (click)=\"OnHeaderSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span class=\"search-btn-text desktop-only\">Search everything</span>\n <kbd class=\"search-btn-kbd desktop-only\" aria-hidden=\"true\">{{ OmnibarShortcutLabel }}</kbd>\n </button>\n } @else {\n <div class=\"shell-search-bar desktop-only\">\n <mj-search-composite\n #shellSearchComposite\n Placeholder=\"Search everything...\"\n [ShowShortcutHint]=\"true\"\n [EnablePreview]=\"ShowSearchPreview\"\n [EnableRecent]=\"true\"\n [MaxPreviewResults]=\"8\"\n [ShowPromo]=\"ShowOmnibarPromo\"\n [PromoText]=\"OmnibarPromoText\"\n (ResultSelected)=\"OnSearchResultSelected($event)\"\n (SearchSubmitted)=\"OnSearchSubmitted($event)\"\n (SeeAllRequested)=\"OnSeeAllSearch($event)\"\n (RecentRecordSelected)=\"OnSearchResultSelected($event)\"\n (PromoAccepted)=\"OnOmnibarPromoAccepted($event)\"\n (PromoDismissed)=\"OnOmnibarPromoDismissed()\">\n </mj-search-composite>\n </div>\n <button class=\"search-btn search-btn-mobile\" title=\"Search everything\"\n [attr.aria-label]=\"'Search everything (' + OmnibarShortcutLabel + ')'\"\n (click)=\"OnHeaderSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n </button>\n }\n }\n <!-- Global Activity indicator -->\n @if (activityItems.length > 0) {\n <div class=\"activity-wrap\">\n <button class=\"icon-btn desktop-only activity-btn\" title=\"Activity\" (click)=\"toggleActivity($event)\">\n <i class=\"fa-solid fa-wave-square\" [class.activity-pulse]=\"activityRunningCount > 0\"></i>\n @if (activityRunningCount > 0) {\n <span class=\"activity-badge\">{{ activityRunningCount }}</span>\n }\n </button>\n @if (activityOpen) {\n <div class=\"activity-drawer\" (click)=\"$event.stopPropagation()\">\n <div class=\"activity-drawer-head\">\n <span>Activity</span>\n <button class=\"activity-clear\" (click)=\"clearFinishedActivity()\" title=\"Clear finished\">\n <i class=\"fa-solid fa-broom\"></i>\n </button>\n </div>\n <div class=\"activity-list\">\n @for (a of activityItems; track a.ID) {\n <div class=\"activity-row\" [class.is-error]=\"a.Status === 'error'\">\n <i class=\"activity-row-icon\"\n [class]=\"a.Status === 'running' ? 'fa-solid fa-spinner fa-spin'\n : a.Status === 'error' ? 'fa-solid fa-circle-exclamation'\n : 'fa-solid fa-circle-check'\"></i>\n <div class=\"activity-row-body\">\n <div class=\"activity-row-label\">{{ a.Label }}</div>\n @if (a.Detail) { <div class=\"activity-row-detail\">{{ a.Detail }}</div> }\n @if (a.Status === 'running' && a.Progress != null) {\n <div class=\"activity-bar\"><div class=\"activity-bar-fill\" [style.width.%]=\"a.Progress\"></div></div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n @if (ShowNotifications) {\n <button class=\"icon-btn desktop-only notification-btn\" title=\"Notifications\" (click)=\"showNotifications()\">\n <i class=\"fa-solid fa-bell\"></i>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n }\n <!-- Nav Bar Apps: Left of User Menu -->\n @if (leftOfUserMenuApps.length > 0) {\n <div class=\"nav-bar-apps left-of-user-menu\">\n @for (app of leftOfUserMenuApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <div class=\"user-menu\">\n <button class=\"avatar-btn\" (click)=\"toggleUserMenu($event)\"\n [attr.aria-label]=\"avatarAriaLabel\"\n [attr.aria-expanded]=\"userMenuVisible\">\n @if (userImageURL) {\n <img [src]=\"userImageURL\" alt=\"\" aria-hidden=\"true\" class=\"avatar-img\" />\n } @else {\n <div class=\"icon-fallback\" aria-hidden=\"true\">\n <i [class]=\"userIconClass || 'fa-solid fa-user'\"></i>\n </div>\n }\n </button>\n <!-- User Context Menu (Dynamic) -->\n @if (userMenuVisible) {\n <div class=\"user-context-menu\"\n [class.menu-fade]=\"getUserMenuOptions()?.animationStyle === 'fade'\"\n [class.menu-slide]=\"getUserMenuOptions()?.animationStyle === 'slide'\">\n <!-- User Header -->\n @if (getUserMenuOptions()?.showUserName) {\n <div class=\"user-menu-header\">\n <div class=\"user-info\">\n <span class=\"user-name\">{{ getUserDisplayInfo()?.name || userName }}</span>\n @if (getUserMenuOptions()?.showUserEmail && getUserDisplayInfo()?.email) {\n <span class=\"user-email\">\n {{ getUserDisplayInfo()?.email }}\n </span>\n }\n @if (getUserDisplayInfo()?.subtitle) {\n <span class=\"user-subtitle\">{{ getUserDisplayInfo()?.subtitle }}</span>\n }\n </div>\n </div>\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Dynamic Menu Items -->\n @for (element of userMenuElements; track element) {\n <!-- Divider -->\n @if (isMenuDivider(element)) {\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Menu Item -->\n @if (!isMenuDivider(element)) {\n @if (asMenuItem(element); as item) {\n @if (item.id === 'toggle-theme') {\n <div class=\"user-menu-item theme-toggle-item\"\n [title]=\"item.tooltip || ''\"\n (click)=\"onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n <div class=\"theme-toggle-track\" [class.dark]=\"IsDarkMode\">\n <div class=\"theme-toggle-thumb\">\n <i [class]=\"IsDarkMode ? 'fa-solid fa-moon' : 'fa-solid fa-sun'\" class=\"theme-toggle-icon\"></i>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"user-menu-item\"\n [class.disabled]=\"!item.enabled\"\n [class.danger]=\"item.cssClass === 'danger'\"\n [style.color]=\"item.color || null\"\n [title]=\"item.tooltip || ''\"\n (click)=\"item.enabled && onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"menu-shortcut\">{{ item.shortcut }}</span>\n }\n </div>\n }\n }\n }\n }\n </div>\n }\n </div>\n </div>\n </header>\n\n <!-- Search Popup -->\n @if (isSearchOpen) {\n <div class=\"search-popup-overlay\" (click)=\"closeSearch()\"></div>\n }\n <div class=\"search-popup\" [class.open]=\"isSearchOpen\" [attr.inert]=\"isSearchOpen ? null : ''\">\n <div class=\"search-popup-content\" (click)=\"$event.stopPropagation()\">\n <mj-dropdown\n [Data]=\"searchableEntities\"\n TextField=\"Name\"\n ValueField=\"ID\"\n AriaLabel=\"Search in\"\n class=\"search-entity-dropdown\"\n [(ngModel)]=\"selectedEntity\">\n </mj-dropdown>\n <input\n type=\"text\"\n #searchInput\n placeholder=\"Search...\"\n aria-label=\"Search\"\n class=\"mj-input search-input\"\n (keydown.enter)=\"onSearch($event)\"\n />\n <button class=\"search-submit-btn\" (click)=\"onSearch($event)\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n </div>\n </div>\n\n <!-- Mobile Navigation Drawer -->\n @if (mobileNavOpen) {\n <div class=\"mobile-nav-overlay\" (click)=\"closeMobileNav()\"></div>\n }\n <div class=\"mobile-nav-drawer\" id=\"mj-mobile-nav-drawer\" #mobileNavDrawer\n [class.open]=\"mobileNavOpen\" [attr.inert]=\"mobileNavOpen ? null : ''\">\n <div class=\"mobile-nav-header\">\n <span>Navigation</span>\n <button class=\"close-btn\" (click)=\"closeMobileNav()\" aria-label=\"Close navigation\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n @if (activeApp && ShowAppNav) {\n <div class=\"mobile-nav-content\">\n <div class=\"mobile-nav-section-title\">{{ activeApp.Name }}</div>\n <mj-app-nav\n [app]=\"activeApp\"\n [OverflowEnabled]=\"false\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n </div>\n }\n <!-- Records style: the desktop pill lives in the (mobile-hidden) app nav,\n so the drawer carries its own entry \u2014 without it, open records are\n unreachable on mobile except by re-opening them. Resumes the\n last-viewed record and closes the drawer. -->\n @if (RecordTabsStyle) {\n <div class=\"mobile-nav-records\">\n <mj-records-hub-pill (Activated)=\"closeMobileNav()\"></mj-records-hub-pill>\n </div>\n }\n <div class=\"mobile-nav-footer\">\n <button class=\"mobile-nav-action\" title=\"Search\" (click)=\"toggleSearch(); closeMobileNav()\">\n <i class=\"fa-solid fa-search\"></i>\n <span>Search</span>\n </button>\n @if (ShowNotifications) {\n <button class=\"mobile-nav-action\" title=\"Notifications\" (click)=\"showNotifications(); closeMobileNav()\">\n <i class=\"fa-solid fa-bell\"></i>\n <span>Notifications</span>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge-mobile\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Tab Container - with dynamic tab bar visibility -->\n <mj-tab-container\n id=\"mj-main-content\"\n role=\"main\"\n tabindex=\"-1\"\n [class.hide-tab-bar]=\"!tabBarVisible\"\n (firstResourceLoadComplete)=\"onFirstResourceLoadComplete()\"\n (layoutInitError)=\"handleLayoutError()\">\n </mj-tab-container>\n</div>\n\n<!-- Loading State -->\n@if (loading) {\n <div class=\"shell-loading\">\n <mj-loading\n [text]=\"currentLoadingText\"\n [textColor]=\"currentLoadingTextColor\"\n [logoColor]=\"currentLoadingColor\"\n [logoGradient]=\"currentLoadingGradient\"\n [animation]=\"currentLoadingAnimation\"\n size=\"large\">\n </mj-loading>\n @if (ShowResetOption) {\n <div class=\"loading-reset-panel\">\n <p class=\"loading-reset-message\">\n <i class=\"fa-regular fa-clock\"></i>\n Taking longer than expected\n </p>\n <p class=\"loading-reset-hint\">\n This can happen after updates or due to cached data issues.\n </p>\n <button class=\"loading-reset-btn\" (click)=\"ResetApplication()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i>\n Reset\n </button>\n </div>\n } @else {\n <div class=\"loading-version\">MemberJunction \u00B7 v{{ MJVersion }}</div>\n }\n </div>\n}\n\n<!-- App Access Error Dialog -->\n<mj-app-access-dialog\n #appAccessDialog\n (result)=\"onAppAccessDialogResult($event)\">\n</mj-app-access-dialog>\n\n<!-- Command Palette -->\n<mj-command-palette\n [ShowSearch]=\"ShowSearchBar\"\n (AppSelected)=\"onAppSwitch($event)\"\n (KnowledgeSearchRequested)=\"OnCommandPaletteSearch($event)\"></mj-command-palette>\n\n<!-- Unified command palette (Shell.Omnibar.Enabled). Rendered only while the search bar is:\n when Instance Config or the host's chrome policy hides search, the palette must not\n remain reachable through Ctrl/Cmd+K. -->\n@if (ShowSearchBar) {\n <mj-omnibar-palette #omnibarPalette (SettingsRequested)=\"OnPaletteSettingsRequested()\"></mj-omnibar-palette>\n}\n\n<!-- Legacy universal search as a Spotlight-style overlay \u2014 omnibar-off MOBILE only\n (on desktop the inline header composite owns the experience; this opens from\n the mobile icon or Ctrl/Cmd+K when the composite isn't visible).\n The shell owns the Ctrl/Cmd+K chord, so the overlay's own global shortcut is off. -->\n@if (ShowSearchBar && !UseOmnibar) {\n <mj-search-overlay\n [(IsOpen)]=\"LegacySearchOpen\"\n Placeholder=\"Search everything...\"\n [EnableGlobalShortcut]=\"false\"\n [ShowPromo]=\"ShowOmnibarPromo\"\n [PromoText]=\"OmnibarPromoText\"\n (ResultSelected)=\"OnOverlayResultSelected($event)\"\n (SeeAllRequested)=\"OnOverlaySeeAll($event)\"\n (PromoAccepted)=\"OnOmnibarPromoAccepted($event)\"\n (PromoDismissed)=\"OnOmnibarPromoDismissed()\">\n </mj-search-overlay>\n}\n\n<!-- Pin Progress Overlay -->\n@if (PinProgressVisible) {\n <div class=\"pin-progress-backdrop\">\n <div class=\"pin-progress-modal\">\n <mj-loading [showText]=\"false\" size=\"small\"></mj-loading>\n <span class=\"pin-progress-text\">{{ PinProgressText }}</span>\n </div>\n </div>\n}\n", styles: [":host {\n display: block;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* MJ Logo - uses design tokens for theme switching & white-labeling.\n Geometry follows --mj-logo-width/-height so a differently-shaped org mark fits;\n --mj-logo-plate-bg is set by the theme engine only when a single-variant logo needs\n a legibility plate in the mode it wasn't designed for (org-theming Phase 4). */\n.mj-logo {\n width: var(--mj-logo-width, 32px);\n height: var(--mj-logo-height, 18px);\n background-image: var(--mj-logo-mark);\n background-color: var(--mj-logo-plate-bg, transparent);\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n border-radius: var(--mj-radius-sm, 4px);\n flex-shrink: 0;\n}\n\n/* Skip link (WCAG 2.4.1) \u2014 off-screen until focused, then pinned over the header.\n It is the first focusable element in the shell; consuming apps should drop their\n own skip link on upgrade rather than stacking a second one in front of this. */\n.mj-skip-link {\n position: absolute;\n top: var(--mj-space-2);\n left: var(--mj-space-2);\n z-index: 100000;\n padding: var(--mj-space-2) var(--mj-space-4);\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n text-decoration: none;\n transform: translateY(-200%);\n transition: transform var(--mj-transition-fast);\n}\n.mj-skip-link:focus,\n.mj-skip-link:focus-visible {\n transform: translateY(0);\n outline: var(--mj-ring-width) solid var(--mj-focus-ring-color);\n outline-offset: var(--mj-ring-offset);\n}\n\n/* The main landmark is also the skip-link target; focusing it programmatically must\n not paint a ring on a region the user did not tab to. */\nmj-tab-container:focus {\n outline: none;\n}\n\n.shell-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n overflow: hidden;\n position: relative;\n}\n\n/* Hide shell container while loading - allows tab container to render and load\n first resource in background while shell loading indicator is visible */\n.shell-container.hidden {\n visibility: hidden;\n position: absolute;\n pointer-events: none;\n}\n\nmj-tab-container {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.2s ease-in-out;\n}\n\n/* Hide Golden Layout tab headers when only one tab \u2014 scoped to the MAIN\n region: the RECORDS region is a separate Golden Layout whose tab strip is\n its whole point (switching between open records) and must never be hidden\n by the main bar's visibility policy. */\nmj-tab-container.hide-tab-bar ::ng-deep .main-region .lm_header {\n display: none;\n}\n\n/* Show tab headers when multiple tabs */\nmj-tab-container:not(.hide-tab-bar) ::ng-deep .main-region .lm_header {\n opacity: 1;\n max-height: 40px;\n transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out;\n}\n\n/* Adjust content area height when tabs hidden */\nmj-tab-container.hide-tab-bar ::ng-deep .main-region .lm_content {\n height: 100% !important;\n}\n\n/* Ensure smooth transitions */\nmj-tab-container ::ng-deep .lm_stack {\n transition: all 0.2s ease-in-out;\n}\n\n.shell-header {\n height: 60px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 var(--mj-space-4);\n gap: var(--mj-space-4);\n box-shadow: var(--mj-shadow-sm);\n flex-shrink: 0;\n /* Creates a stacking context so children (search bar z-index: 99999, dropdown)\n are contained within this level. Modal overlays (z-index: 1000+) sit above. */\n position: relative;\n z-index: 500;\n}\n\n/* Nav Bar Apps - permanent app icons in the header */\n.nav-bar-apps {\n display: flex;\n align-items: center;\n gap: var(--mj-space-1);\n}\n\n.nav-bar-apps.left-of-switcher {\n /* No extra margin - header gap handles spacing */\n /* This prevents the app switcher from shifting when icons are hidden */\n}\n\n.nav-bar-apps.left-of-user-menu {\n margin-right: var(--mj-space-2);\n}\n\n.nav-bar-app-btn {\n --app-color: var(--mj-text-muted);\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-lg);\n transition: all var(--mj-transition-fast);\n position: relative;\n}\n\n.nav-bar-app-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Active = brand-primary (matches nav pills + switcher items); the app's\n metadata color is reserved for identity accents, never state.\n Shared active-tint recipe. */\n.nav-bar-app-btn.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n\n.nav-bar-app-btn i {\n transition: transform var(--mj-transition-fast);\n margin-top: var(--mj-space-0-5); /* Align with app switcher icon */\n}\n\n.nav-bar-app-btn:hover i {\n transform: scale(1.1);\n}\n\n.spacer {\n flex: 1;\n}\n\n/* Priority+ overflow support: on desktop the app nav owns the header's free\n middle space (so it knows exactly how much room its items have) and the\n spacer stands down. On mobile the nav is display:none'd, so the spacer's\n flex: 1 keeps the action cluster right-aligned. When the nav isn't rendered\n at all (app-locked sessions, Shell.AppNav.Enabled off), the sibling selector\n doesn't match and the spacer behaves as before. */\n@media (min-width: 769px) {\n mj-app-nav.desktop-nav {\n flex: 1 1 auto;\n min-width: 0;\n }\n mj-app-nav.desktop-nav + .spacer {\n flex: 0 0 0px;\n }\n}\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-lg);\n transition: background var(--mj-transition-fast);\n}\n\n.icon-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Keyboard focus parity for all header controls (the search bar already has\n its own ring at .search-btn:focus-visible). */\n.nav-bar-app-btn:focus-visible,\n.icon-btn:focus-visible,\n.hamburger-btn:focus-visible,\n.user-menu .avatar-btn:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n/* Universal search: a static-width bar styled as an input, anchored on the\n right with the action cluster. Deliberately NOT resizable on hover/focus \u2014\n chrome that shifts on interaction was rejected in the 2026-07 review. It's a\n button (typing happens in the overlay it opens), so no focus/caret styling. */\n.search-btn {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n width: 260px;\n height: 36px;\n padding: 0 var(--mj-space-2-5) 0 var(--mj-space-3-5);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n font-family: inherit;\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.search-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n}\n\n.search-btn:focus-visible {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n/* Omnibar-off mobile trigger: hidden on desktop (the inline composite renders\n instead); the 768px block flips it on as the icon circle. */\n.search-btn-mobile {\n display: none;\n}\n\n/* Omnibar-off desktop: the inline search composite, same slot + width as the\n omnibar bar so the opt-in toggle doesn't move the header. */\n.shell-search-bar {\n position: relative;\n width: 260px;\n z-index: 99999;\n}\n\n/* The suggest dropdown needs more width than its 260px anchor. Pin it to the\n anchor's right edge and let it grow leftward \u2014 the bar is right-anchored, so\n leftward growth stays on screen. */\n.shell-search-bar ::ng-deep .suggest-dropdown {\n left: auto;\n right: 0;\n width: min(560px, calc(100vw - var(--mj-space-6)));\n}\n\n.search-btn i {\n font-size: var(--mj-text-sm);\n flex-shrink: 0;\n}\n\n.search-btn .search-btn-text {\n flex: 1;\n min-width: 0;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* kbd chip: deliberately mirrors the omnibar palette's .ob-kbd chrome\n (10.5px / 5px radius / bottom-heavy border) so the affordance and the\n surface it summons read as one system. */\n.search-btn .search-btn-kbd {\n flex-shrink: 0;\n white-space: nowrap;\n font-family: inherit;\n font-size: 10.5px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-bottom-width: 2px;\n border-radius: 5px;\n padding: 1.5px var(--mj-space-1-5);\n}\n\n/* Notification button with badge */\n.notification-btn {\n position: relative;\n}\n\n.notification-badge {\n position: absolute;\n top: 4px;\n right: 4px;\n min-width: 16px;\n height: 16px;\n padding: 0 var(--mj-space-1);\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: var(--mj-leading-none);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.user-menu {\n position: relative;\n}\n\n.user-menu .avatar-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: box-shadow var(--mj-transition-fast), transform var(--mj-transition-fast);\n padding: 0;\n overflow: hidden;\n}\n\n.user-menu .avatar-btn:hover {\n transform: scale(1.05);\n}\n\n/* Icon fallback styling - shows gray circle with icon */\n.user-menu .avatar-btn .icon-fallback {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-surface-hover);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: border-color var(--mj-transition-fast);\n}\n\n.user-menu .avatar-btn:hover .icon-fallback {\n border-color: var(--mj-brand-primary);\n}\n\n.user-menu .avatar-btn .icon-fallback i {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n}\n\n/* Avatar image - replaces the gray circle entirely */\n.user-menu .avatar-btn .avatar-img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n box-shadow: var(--mj-shadow-sm);\n}\n\n.user-menu .avatar-btn:hover .avatar-img {\n box-shadow: var(--mj-shadow-md);\n}\n\n/* User Menu Header */\n.user-menu-header {\n padding: var(--mj-space-3) var(--mj-space-4);\n background: var(--mj-bg-surface-sunken);\n}\n\n.user-menu-header .user-info {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-0-5);\n}\n\n.user-menu-header .user-name {\n font-weight: var(--mj-font-semibold);\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.user-menu-header .user-email {\n font-size: var(--mj-text-xs);\n color: var(--mj-text-muted);\n}\n\n.user-menu-header .user-subtitle {\n font-size: 11px;\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n/* User Context Menu */\n.user-context-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: var(--mj-space-2);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 180px;\n /* Never wider than the viewport, never taller than what is left below the 60px header:\n a short window (or a phone) used to leave the bottom items unreachable, because the\n menu had no height limit and clipped instead of scrolling. */\n max-width: min(360px, calc(100vw - 2 * var(--mj-space-4)));\n max-height: calc(100vh - 60px - 3 * var(--mj-space-2));\n max-height: calc(100dvh - 60px - 3 * var(--mj-space-2));\n z-index: 10000;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.user-menu-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n transition: background var(--mj-transition-fast);\n}\n\n.user-menu-item i {\n width: 18px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n}\n\n.user-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.user-menu-item.danger {\n color: var(--mj-status-error);\n}\n\n.user-menu-item.danger i {\n color: var(--mj-status-error);\n}\n\n.user-menu-item.danger:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface-elevated));\n}\n\n.user-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: var(--mj-space-1) 0;\n}\n\n/* Menu item disabled state */\n.user-menu-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.user-menu-item.disabled:hover {\n background: transparent;\n}\n\n/* Theme Toggle Switch */\n.theme-toggle-item {\n cursor: pointer;\n}\n\n.theme-toggle-track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: var(--mj-radius-full);\n background: var(--mj-bg-surface-active);\n margin-left: auto;\n transition: background var(--mj-transition-base);\n flex-shrink: 0;\n}\n\n.theme-toggle-track.dark {\n background: var(--mj-brand-primary);\n}\n\n.theme-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-sm);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform var(--mj-transition-base);\n}\n\n.theme-toggle-track.dark .theme-toggle-thumb {\n transform: translateX(18px);\n}\n\n.theme-toggle-icon {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.theme-toggle-track.dark .theme-toggle-icon {\n color: var(--mj-text-inverse);\n}\n\n/* Menu shortcut hint */\n.user-menu-item .menu-shortcut {\n margin-left: auto;\n font-size: 11px;\n color: var(--mj-text-disabled);\n font-family: monospace;\n}\n\n/* Menu animations */\n.user-context-menu.menu-fade {\n animation: menuFadeIn 0.15s ease-out;\n}\n\n.user-context-menu.menu-slide {\n animation: menuSlideIn 0.2s ease-out;\n}\n\n@keyframes menuFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes menuSlideIn {\n from {\n opacity: 0;\n transform: translateY(-16px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.shell-loading {\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n z-index: 99999;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-bottom: 15vh;\n background: var(--mj-bg-page);\n}\n\n.loading-version {\n margin-top: var(--mj-space-8, 24px);\n font-size: 11px;\n color: var(--mj-text-muted);\n letter-spacing: 0.4px;\n opacity: 0.6;\n user-select: none;\n}\n\n/* Loading Recovery Reset Panel */\n.loading-reset-panel {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--mj-space-3);\n margin-top: var(--mj-space-10);\n padding: var(--mj-space-8);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-md);\n max-width: 420px;\n animation: resetPanelFadeIn 0.4s ease-out;\n}\n\n.loading-reset-message {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n\n.loading-reset-message i {\n font-size: var(--mj-text-lg);\n color: var(--mj-status-warning);\n}\n\n.loading-reset-hint {\n margin: 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n text-align: center;\n line-height: var(--mj-leading-relaxed);\n}\n\n.loading-reset-btn {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-6);\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-medium);\n font-family: var(--mj-font-family);\n color: var(--mj-text-inverse);\n background: var(--mj-brand-primary);\n border: 1px solid var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.loading-reset-btn:hover {\n background: var(--mj-brand-primary-hover);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: var(--mj-shadow-md);\n}\n\n.loading-reset-btn:active {\n background: var(--mj-brand-primary-active);\n color: var(--mj-text-inverse);\n transform: scale(0.98);\n}\n\n.loading-reset-btn i {\n font-size: var(--mj-text-sm);\n}\n\n@keyframes resetPanelFadeIn {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Hamburger button - hidden on desktop */\n.hamburger-btn {\n display: none;\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: none;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-xl);\n transition: background var(--mj-transition-fast);\n}\n.hamburger-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile Navigation Overlay */\n.mobile-nav-overlay {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 9998;\n}\n\n/* Mobile Navigation Drawer */\n/* `visibility` is transitioned alongside `transform` so the closed drawer is genuinely\n hidden \u2014 off-screen alone leaves its controls hit-testable and visible to\n checkVisibility(), which is what let 6 of its 8 controls keep taking focus. The\n delay holds visibility through the slide-out, then drops it; opening is instant\n (0s) so the drawer is present for the whole slide-in. `inert` (set in the template)\n is the actual focus guard; this keeps the CSS from disagreeing with it. */\n.mobile-nav-drawer {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 85vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-xl);\n z-index: 9999;\n flex-direction: column;\n transform: translateX(-100%);\n visibility: hidden;\n transition: transform var(--mj-transition-slow), visibility 0s linear 300ms;\n}\n.mobile-nav-drawer.open {\n transform: translateX(0);\n visibility: visible;\n transition: transform var(--mj-transition-slow), visibility 0s linear 0s;\n}\n\n/* Reduced motion: no slide, so nothing to hold visibility open for. */\n@media (prefers-reduced-motion: reduce) {\n .mobile-nav-drawer,\n .mobile-nav-drawer.open {\n transition: none;\n }\n}\n\n.mobile-nav-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-4) var(--mj-space-5);\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n.mobile-nav-header span {\n font-weight: var(--mj-font-semibold);\n font-size: var(--mj-text-base);\n color: var(--mj-text-primary);\n}\n.mobile-nav-header .close-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-lg);\n transition: background var(--mj-transition-fast);\n}\n.mobile-nav-header .close-btn:hover {\n background: var(--mj-bg-surface-active);\n}\n\n.mobile-nav-content {\n flex: 1;\n overflow-y: auto;\n padding: var(--mj-space-4) 0;\n}\n\n.mobile-nav-section-title {\n padding: var(--mj-space-2) var(--mj-space-5);\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n/* Records entry in the mobile drawer (records-style record opens) \u2014\n the desktop pill lives in the mobile-hidden app nav, so the drawer\n carries its own touch-friendly instance. */\n.mobile-nav-records {\n padding: var(--mj-space-2) var(--mj-space-3);\n border-top: 1px solid var(--mj-border-default);\n}\n.mobile-nav-records .records-pill {\n width: 100%;\n min-height: 48px; /* Touch-target parity with drawer nav items */\n justify-content: flex-start;\n}\n\n.mobile-nav-footer {\n border-top: 1px solid var(--mj-border-default);\n padding: var(--mj-space-3) var(--mj-space-4);\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-1);\n}\n\n.mobile-nav-action {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border: none;\n background: none;\n cursor: pointer;\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-secondary);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n transition: background var(--mj-transition-fast);\n width: 100%;\n text-align: left;\n}\n.mobile-nav-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n.mobile-nav-action i {\n font-size: var(--mj-text-base);\n width: 20px;\n text-align: center;\n}\n\n/* Mobile Responsive Styles */\n@media (max-width: 768px) {\n .hamburger-btn {\n display: flex;\n }\n\n .desktop-nav {\n display: none !important;\n }\n\n .desktop-only {\n display: none !important;\n }\n\n .mobile-nav-overlay {\n display: block;\n }\n\n .mobile-nav-drawer {\n display: flex;\n }\n\n .shell-header {\n padding: 0 var(--mj-space-3);\n gap: var(--mj-space-2);\n }\n\n /* Keep the right-edge cluster (chat/nav-bar app icons + avatar) on one row.\n Without pinning this, the icons can wrap and the avatar drops onto a second\n line, stacking under the chat icon. */\n .header-actions {\n flex-direction: row;\n flex-wrap: nowrap;\n }\n .header-actions .user-menu,\n .header-actions .nav-bar-apps,\n .header-actions .search-btn {\n flex-shrink: 0;\n }\n\n /* Search bar collapses to the icon-only circle on small viewports \u2014\n the placeholder text + kbd chip are already hidden via .desktop-only.\n The omnibar-off mobile trigger (.search-btn-mobile) becomes visible here;\n its .desktop-only composite sibling is hidden by the rule above. */\n .search-btn {\n display: flex;\n width: 40px;\n height: 40px;\n padding: 0;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: none;\n }\n .search-btn i {\n font-size: var(--mj-text-lg);\n }\n .search-btn:hover {\n background: var(--mj-bg-surface-hover);\n border: none;\n }\n}\n\n/* Settings Full-Screen Window Styles */\n::ng-deep .settings-fullscreen-window {\n /* Remove window chrome for full-screen feel */\n box-shadow: none !important;\n border: none !important;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar {\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n padding: var(--mj-space-3) var(--mj-space-4);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-title {\n font-size: var(--mj-text-lg);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-actions {\n gap: var(--mj-space-1);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action {\n width: 32px;\n height: 32px;\n border-radius: var(--mj-radius-md);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: var(--mj-bg-page);\n}\n\n/* Hide minimize/maximize buttons - only show close */\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Minimize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Maximize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-minimize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-maximize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window {\n display: none !important;\n}\n\n/* Universal search is a right-anchored bar affordance in header-actions\n (2026-07 design review round 2): both experiences still open as overlays \u2014\n the bar is a trigger, not an inline input. Icon-only under 768px. */\n\n/* ========================================\n SEARCH POPUP\n ======================================== */\n\n.search-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 9999;\n animation: fadeIn 0.15s ease;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.search-popup {\n position: absolute;\n top: 60px; /* Below header */\n right: 16px;\n width: 480px;\n max-width: calc(100vw - 32px);\n background: var(--mj-bg-surface-elevated);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-xl);\n z-index: 10000;\n opacity: 0;\n transform: translateY(-10px);\n pointer-events: none;\n visibility: hidden;\n transition: opacity var(--mj-transition-base), transform var(--mj-transition-base), visibility 0s linear 200ms;\n}\n\n.search-popup.open {\n opacity: 1;\n transform: translateY(0);\n pointer-events: all;\n visibility: visible;\n transition: opacity var(--mj-transition-base), transform var(--mj-transition-base), visibility 0s linear 0s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .search-popup,\n .search-popup.open {\n transition: none;\n }\n}\n\n.search-popup-content {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-3);\n}\n\n.search-entity-dropdown {\n min-width: 140px;\n max-width: 180px;\n flex-shrink: 0;\n}\n\n.search-input {\n flex: 1;\n min-width: 0;\n height: 40px;\n padding: var(--mj-space-2) var(--mj-space-3);\n font-size: var(--mj-text-sm);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n outline: none;\n display: block !important;\n box-sizing: border-box;\n}\n\n.search-input:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.search-submit-btn {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n border: none;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--mj-text-base);\n transition: background var(--mj-transition-fast);\n flex-shrink: 0;\n}\n\n.search-submit-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.search-submit-btn:active {\n background: var(--mj-brand-primary-hover);\n}\n\n/* Mobile notification badge */\n.notification-badge-mobile {\n position: absolute;\n top: 8px;\n right: 8px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: var(--mj-leading-none);\n box-shadow: var(--mj-shadow-sm);\n}\n\n/* Mobile-specific search popup adjustments */\n@media (max-width: 768px) {\n .search-popup {\n top: 60px;\n left: 16px;\n right: 16px;\n width: auto;\n max-width: none;\n }\n\n .search-popup-content {\n flex-wrap: wrap;\n }\n\n .search-entity-dropdown {\n width: 100%;\n }\n\n .search-input {\n flex: 1;\n min-width: 0;\n }\n}\n\n/* ========================================\n PIN PROGRESS OVERLAY\n ======================================== */\n.pin-progress-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: color-mix(in srgb, var(--mj-text-primary) 40%, transparent);\n z-index: 100000;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: pinBackdropIn 0.2s ease;\n}\n\n@keyframes pinBackdropIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.pin-progress-modal {\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-xl);\n padding: var(--mj-space-7) var(--mj-space-10);\n display: flex;\n align-items: center;\n gap: var(--mj-space-4);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--mj-text-primary) 30%, transparent);\n animation: pinModalIn 0.25s ease;\n}\n\n@keyframes pinModalIn {\n from { opacity: 0; transform: scale(0.9) translateY(8px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n}\n\n.pin-progress-text {\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n/* ============================================================\n Global Activity indicator (P3)\n ============================================================ */\n.activity-wrap { position: relative; display: inline-flex; }\n.activity-btn { position: relative; }\n.activity-btn .activity-pulse { color: var(--mj-brand-primary); }\n.activity-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 var(--mj-space-1);\n border-radius: var(--mj-radius-md);\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n line-height: 16px;\n text-align: center;\n}\n.activity-drawer {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 320px;\n max-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: 0 8px 28px color-mix(in srgb, var(--mj-text-primary) 18%, transparent);\n z-index: 1000;\n overflow: hidden;\n}\n.activity-drawer-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-2-5) var(--mj-space-3);\n font-weight: var(--mj-font-bold);\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-default);\n}\n.activity-clear {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.8rem;\n padding: var(--mj-space-1) var(--mj-space-1-5);\n border-radius: var(--mj-radius-md);\n}\n.activity-clear:hover { color: var(--mj-text-primary); background: var(--mj-bg-surface-hover); }\n.activity-list { overflow-y: auto; padding: var(--mj-space-1-5); }\n.activity-row {\n display: flex;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2);\n border-radius: var(--mj-radius-md);\n}\n.activity-row:hover { background: var(--mj-bg-surface-hover); }\n.activity-row-icon { margin-top: var(--mj-space-0-5); color: var(--mj-brand-primary); font-size: 0.85rem; }\n.activity-row.is-error .activity-row-icon { color: var(--mj-status-error); }\n.activity-row-body { flex: 1; min-width: 0; }\n.activity-row-label { font-size: 0.82rem; font-weight: var(--mj-font-semibold); color: var(--mj-text-primary); }\n.activity-row-detail { font-size: 0.72rem; color: var(--mj-text-muted); margin-top: var(--mj-space-px); }\n.activity-bar {\n margin-top: var(--mj-space-1-5);\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n}\n.activity-bar-fill { height: 100%; background: var(--mj-brand-primary); transition: width var(--mj-transition-slow); }\n\n"] }]
4180
+ }], () => [{ type: i1.ApplicationManager }, { type: i1.WorkspaceStateManager }, { type: i1.GoldenLayoutManager }, { type: i1.TabService }, { type: i2.NavigationService }, { type: i3.ActivatedRoute }, { type: i3.Router }, { type: i4.MJAuthBase }, { type: i0.ChangeDetectorRef }, { type: i5.UserAvatarService }, { type: i6.UserSharingCenterDialogService }, { type: i7.AboutDialogService }, { type: i8.ProfileDialogService }, { type: i0.ViewContainerRef }, { type: i2.TitleService }, { type: i2.DeveloperModeService }, { type: i9.StartupValidationService }, { type: i10.CommandPaletteService }, { type: i2.ThemeService }, { type: i2.HomeAppPinService }, { type: i11.FileOpenService }, { type: i12.FeedbackDialogService }, { type: i12.FeedbackService }, { type: i2.ActivityService }], { SearchInput: [{
3782
4181
  type: ViewChild,
3783
4182
  args: ['searchInput']
3784
- }], mobileNavDrawer: [{
4183
+ }], MobileNavDrawer: [{
3785
4184
  type: ViewChild,
3786
4185
  args: ['mobileNavDrawer']
3787
- }], mobileNavToggle: [{
4186
+ }], MobileNavToggle: [{
3788
4187
  type: ViewChild,
3789
4188
  args: ['mobileNavToggle']
3790
- }], omnibarPalette: [{
4189
+ }], OmnibarPalette: [{
3791
4190
  type: ViewChild,
3792
4191
  args: ['omnibarPalette']
3793
- }], shellSearchComposite: [{
4192
+ }], ShellSearchComposite: [{
3794
4193
  type: ViewChild,
3795
4194
  args: ['shellSearchComposite']
3796
- }], tabContainerRef: [{
4195
+ }], TabContainerRef: [{
3797
4196
  type: ViewChild,
3798
4197
  args: [TabContainerComponent]
3799
- }], appAccessDialog: [{
4198
+ }], AppAccessDialog: [{
3800
4199
  type: ViewChild,
3801
4200
  args: ['appAccessDialog']
3802
- }], handleGlobalKeyboardShortcuts: [{
3803
- type: HostListener,
3804
- args: ['document:keydown', ['$event']]
3805
4201
  }], OnGlobalKeydown: [{
3806
4202
  type: HostListener,
3807
4203
  args: ['document:keydown', ['$event']]