@trismegisto/spine 2.1.1 → 3.0.0

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 (78) hide show
  1. package/README.md +105 -19
  2. package/fesm2022/trismegisto-spine-audit.mjs +264 -10
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/trismegisto-spine-auto-service-apps.technical-BAKdkVdz.mjs +84 -0
  5. package/fesm2022/trismegisto-spine-auto-service-apps.technical-BAKdkVdz.mjs.map +1 -0
  6. package/fesm2022/trismegisto-spine-auto-service-apps.usage-CAXMP5r8.mjs +87 -0
  7. package/fesm2022/trismegisto-spine-auto-service-apps.usage-CAXMP5r8.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs → trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs} +6 -5
  9. package/fesm2022/trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs.map +1 -0
  10. package/fesm2022/{trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs → trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs} +9 -8
  11. package/fesm2022/trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs.map +1 -0
  12. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs → trismegisto-spine-auto-service-auto-service-audit.component-BIL8tH1I.mjs} +4 -4
  13. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-BIL8tH1I.mjs.map} +1 -1
  14. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs → trismegisto-spine-auto-service-auto-service-client-picker.component-CBt_0Uli.mjs} +3 -3
  15. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs.map → trismegisto-spine-auto-service-auto-service-client-picker.component-CBt_0Uli.mjs.map} +1 -1
  16. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs → trismegisto-spine-auto-service-auto-service-client.component-DAxhZC-a.mjs} +123 -27
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-DAxhZC-a.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-CoJYTtBU.mjs +87 -0
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-CoJYTtBU.mjs.map +1 -0
  20. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-UVMGF7-B.mjs +544 -0
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-UVMGF7-B.mjs.map +1 -0
  22. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-Boi-kG0G.mjs +37 -0
  23. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-Boi-kG0G.mjs.map +1 -0
  24. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DmLafUkX.mjs +943 -0
  25. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DmLafUkX.mjs.map +1 -0
  26. package/fesm2022/{trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs → trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs} +24 -3
  27. package/fesm2022/trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs.map +1 -0
  28. package/fesm2022/{trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs → trismegisto-spine-auto-service-client.usage-CcJANb20.mjs} +15 -6
  29. package/fesm2022/trismegisto-spine-auto-service-client.usage-CcJANb20.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service-groups.technical-BlktCM70.mjs +65 -0
  31. package/fesm2022/trismegisto-spine-auto-service-groups.technical-BlktCM70.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Cz5sCKgn.mjs +81 -0
  33. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Cz5sCKgn.mjs.map +1 -0
  34. package/fesm2022/{trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs → trismegisto-spine-auto-service-home.component-5_aji1hf.mjs} +5 -4
  35. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map +1 -0
  36. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs +37 -0
  37. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs.map +1 -0
  38. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs +60 -0
  39. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs.map +1 -0
  40. package/fesm2022/trismegisto-spine-auto-service.mjs +49 -29
  41. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  42. package/fesm2022/trismegisto-spine-layout.mjs +1850 -217
  43. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  44. package/fesm2022/trismegisto-spine-shell.mjs +28 -8
  45. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  46. package/fesm2022/trismegisto-spine.mjs +1163 -87
  47. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  48. package/package.json +6 -6
  49. package/types/trismegisto-spine-audit.d.ts +180 -5
  50. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  51. package/types/trismegisto-spine-auto-service.d.ts +100 -7
  52. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  53. package/types/trismegisto-spine-layout.d.ts +885 -22
  54. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  55. package/types/trismegisto-spine-shell.d.ts +11 -2
  56. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  57. package/types/trismegisto-spine.d.ts +496 -34
  58. package/types/trismegisto-spine.d.ts.map +1 -1
  59. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs +0 -48
  60. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs.map +0 -1
  61. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs +0 -55
  62. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs.map +0 -1
  63. package/fesm2022/trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs.map +0 -1
  64. package/fesm2022/trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs.map +0 -1
  65. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs.map +0 -1
  66. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs +0 -39
  67. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs.map +0 -1
  68. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs +0 -357
  69. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs.map +0 -1
  70. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs +0 -502
  71. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs.map +0 -1
  72. package/fesm2022/trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs.map +0 -1
  73. package/fesm2022/trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs.map +0 -1
  74. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs +0 -39
  75. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs.map +0 -1
  76. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs +0 -54
  77. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs.map +0 -1
  78. package/fesm2022/trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs.map +0 -1
@@ -1,22 +1,22 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ChangeDetectorRef, ChangeDetectionStrategy, Component, InjectionToken, input, signal, computed, Optional, Inject, Injectable, Injector, afterNextRender, ViewChild, effect, HostListener, DOCUMENT, Directive, isDevMode, DestroyRef, output, ElementRef, ViewEncapsulation } from '@angular/core';
2
+ import { inject, ChangeDetectorRef, ChangeDetectionStrategy, Component, InjectionToken, input, signal, computed, Optional, Inject, Injectable, DestroyRef, Injector, afterNextRender, ViewChild, effect, HostListener, DOCUMENT, PLATFORM_ID, untracked, NgZone, isDevMode, Directive, viewChild, output, ElementRef, ViewEncapsulation } from '@angular/core';
3
3
  import * as i3 from '@angular/router';
4
4
  import { RouterLink, Router, NavigationEnd, NavigationSkipped, RouterOutlet } from '@angular/router';
5
5
  import { BehaviorSubject, filter, take, Subject, firstValueFrom } from 'rxjs';
6
- import { IconComponent, IconButtonComponent, LoadingSquareComponent, HermesShortcutService, HERMES_MESSAGES, HeaderComponent as HeaderComponent$1, InputComponent, MenuButtonComponent, AccountAvatarComponent, ModalComponent, createPersistedWorkspaceStore, HERMES_WORKSPACE_PERSISTENCE_CONFIG, findPanelTabs, SidebarComponent as SidebarComponent$1, SidebarNodeComponent, ButtonComponent, CarouselComponent, ShortcutListComponent, CodeInputComponent, RadioComponent, AlertComponent, HermesToastService, TagComponent, SegmentedControlComponent, HermesThemeService, HermesSidebarService, ComboboxComponent, SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent, SelectComponent, ThemePickerComponent, BreadcrumbComponent, TooltipDirective, SkeletonComponent, WorkspaceComponent } from '@trismegisto/angular';
6
+ import { IconComponent, IconButtonComponent, LoadingSquareComponent, HermesShortcutService, HERMES_MESSAGES, HeaderComponent as HeaderComponent$1, InputComponent, MenuButtonComponent, AccountAvatarComponent, ModalComponent, createPersistedWorkspaceStore, HERMES_WORKSPACE_PERSISTENCE_CONFIG, findPanelTabs, SidebarComponent as SidebarComponent$1, SidebarNodeComponent, ButtonComponent, CarouselComponent, ShortcutListComponent, HermesThemeService, HERMES_THEME_CONFIG, HermesSidebarService, HermesToastService, AlertComponent, SegmentedControlComponent, TagComponent, CodeInputComponent, RadioComponent, CheckboxComponent, CodeViewerComponent, SelectComponent, HERMES_SIDEBAR_CONFIG, HermesI18nService, ComboboxComponent, SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent, ThemePickerComponent, ToggletipComponent, BreadcrumbComponent, TooltipDirective, SkeletonComponent, WorkspaceComponent, ToastStackComponent } from '@trismegisto/angular';
7
7
  import { ModalComponent as ModalComponent$1 } from '@trismegisto/spine/modal';
8
- import { NotificationComponent } from '@trismegisto/spine/notification';
8
+ import { NotificationService, AlertType, NotificationComponent } from '@trismegisto/spine/notification';
9
9
  import { SidePanelComponent } from '@trismegisto/spine/side-panel';
10
10
  import * as i2$1 from '@trismegisto/spine';
11
- import { SPINE_MESSAGES, SPINE_CONFIG, ThemeService, resolveSpineLogo, SPINE_NEWS, SpineUserStorage, SPINE_USER_STORAGE_KEYS, spineRoutesOf, routerIdOf, SpinePreferencesService, SPINE_ADMIN_API, ThemePresetService, DensityService, APP_PACKAGE_INFO, SpineQuickAccessService } from '@trismegisto/spine';
11
+ import { SPINE_MESSAGES, SPINE_CONFIG, ThemeService, resolveSpineLogo, SPINE_NEWS, SpineUserStorage, SPINE_USER_STORAGE_KEYS, spineRoutesOf, routerIdOf, SpinePreferencesService, SpineQuickAccessService, DensityService, ThemePresetService, SPINE_IDLE_OPTIONS, SPINE_ADMIN_API, APP_PACKAGE_INFO } from '@trismegisto/spine';
12
12
  import * as i2 from '@angular/common';
13
- import { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
13
+ import { CommonModule, NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser } from '@angular/common';
14
14
  import * as i1$1 from '@angular/forms';
15
15
  import { FormsModule, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
16
16
  import * as i1 from '@orfeu/angular/core';
17
- import { ViewService, AuthorizationService, RealmAppsService, AUTH_CONFIG } from '@orfeu/angular/core';
17
+ import { AuthorizationService, ViewService, RealmAppsService, AUTH_CONFIG } from '@orfeu/angular/core';
18
18
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
19
- import { formatMessage, pluralMessage, urlPath, formatMessageHtml, HERMES_FONTS } from '@trismegisto/primitives';
19
+ import { formatMessage, prefersReducedMotion, motionPreferenceOf, urlPath, pluralMessage, formatMessageHtml, HERMES_FONTS, HERMES_TEXT_SCALES, weekdayNames, FALLBACK_FAMILY } from '@trismegisto/primitives';
20
20
  import { resolveEffectiveRouteChain, ComponentDistributorService, componentRouterId, LoggerService } from '@trismegisto/spine/routing';
21
21
  import { renderIconSvg } from '@trismegisto/icons';
22
22
  import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
@@ -474,15 +474,120 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
474
474
  args: [{ providedIn: 'root' }]
475
475
  }], ctorParameters: () => [] });
476
476
 
477
+ /** Tempo do aviso de falha na tela (segundos). */
478
+ const LOGOUT_FAILED_SECONDS = 8;
479
+ /**
480
+ * Saída da sessão pelo shell: todo botão/atalho de sair do spine passa por aqui (menu do perfil, painel de
481
+ * configurações, `Mod+Shift+L`, tela `/blocked`).
482
+ *
483
+ * - Assina `logoutFailed$` do `@orfeu/angular` (1.3.x) uma vez só: quando o logout no Argos falha (a lib repete uma
484
+ * vez e mantém a sessão), mostra o aviso de erro `session.logoutFailed` (`hm-alert` de erro, `role="alert"`). Vale
485
+ * também para um `logout()` chamado direto pelo app.
486
+ * - `logout()` aguarda o resultado: só apaga os históricos do usuário (`SpineUserStorage.clearHistory()`) quando a
487
+ * saída deu certo. Com falha, nada muda: a pessoa continua na tela, com a sessão e os recentes.
488
+ * - `pending` fica `true` enquanto a saída está em andamento e sempre volta a `false` (os botões não travam).
489
+ *
490
+ * Com `@orfeu/angular` 1.0.x (sem `logoutFailed$`, `logout()` sem retorno) a saída conta como concluída, como antes.
491
+ */
492
+ class SpineLogoutService {
493
+ constructor() {
494
+ this.auth = inject(AuthorizationService);
495
+ this.userStorage = inject(SpineUserStorage);
496
+ this.notifications = inject(NotificationService);
497
+ this.messages = inject(SPINE_MESSAGES);
498
+ this.inFlight = null;
499
+ /** Saída em andamento (o botão mostra "saindo…" / fica desabilitado). */
500
+ this.pending = signal(false, ...(ngDevMode ? [{ debugName: "pending" }] : /* istanbul ignore next */ []));
501
+ const failed$ = this.auth.logoutFailed$;
502
+ this.failureStream = typeof failed$?.subscribe === 'function';
503
+ if (!this.failureStream)
504
+ return;
505
+ const sub = failed$.subscribe(() => this.notifyFailure());
506
+ inject(DestroyRef).onDestroy(() => sub.unsubscribe());
507
+ }
508
+ /**
509
+ * Sai da sessão. Resolve `true` quando saiu (o `@orfeu/angular` já navega para o `/logoff`) e `false` quando o
510
+ * Argos não confirmou: a sessão continua e o aviso de erro aparece. Nunca rejeita. Cliques repetidos durante a
511
+ * saída reaproveitam a mesma tentativa.
512
+ */
513
+ logout() {
514
+ this.inFlight ??= this.run().finally(() => {
515
+ this.inFlight = null;
516
+ this.pending.set(false);
517
+ });
518
+ return this.inFlight;
519
+ }
520
+ async run() {
521
+ this.pending.set(true);
522
+ // Guarda de quem é a sessão antes de sair: depois do logout o token some, e a limpeza precisa do usuário.
523
+ this.userStorage.userId();
524
+ let ok;
525
+ let rejected = false;
526
+ try {
527
+ // 1.3.x: Promise<boolean>; 1.0.x: void (a saída local sempre acontecia).
528
+ ok = (await this.auth.logout()) !== false;
529
+ }
530
+ catch {
531
+ ok = false;
532
+ rejected = true;
533
+ }
534
+ if (ok) {
535
+ this.userStorage.clearHistory();
536
+ }
537
+ else if (rejected || !this.failureStream) {
538
+ // Sem `logoutFailed$` para avisar (ou a chamada nem chegou a resolver): o próprio resultado avisa.
539
+ this.notifyFailure();
540
+ }
541
+ return ok;
542
+ }
543
+ notifyFailure() {
544
+ this.notifications.addNotification({
545
+ timer: LOGOUT_FAILED_SECONDS,
546
+ hermesAlertType: AlertType.DANGER,
547
+ message: this.messages.session.logoutFailed,
548
+ hermesAlertClosable: true,
549
+ });
550
+ }
551
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineLogoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
552
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineLogoutService, providedIn: 'root' }); }
553
+ }
554
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineLogoutService, decorators: [{
555
+ type: Injectable,
556
+ args: [{ providedIn: 'root' }]
557
+ }], ctorParameters: () => [] });
558
+
477
559
  /** Seções que o próprio spine já traz. `key` é também o valor de `?settings=` na URL. */
478
560
  const SPINE_SETTINGS_KEYS = {
479
561
  appearance: 'aparencia',
562
+ /** Tamanho do texto e animações. */
563
+ accessibility: 'acessibilidade',
480
564
  region: 'regiao',
481
565
  shortcuts: 'atalhos',
566
+ /** Novidades e tour guiado. */
567
+ help: 'ajuda',
568
+ /** Tipos de aviso do app e aviso do navegador (`SpineFeatures.notices`). */
569
+ notices: 'notificacoes',
570
+ /** Listas guardadas neste navegador (recentes, buscas, favoritos) e restaurar preferências. */
571
+ data: 'dados',
482
572
  account: 'conta',
573
+ /** Verificação em duas etapas e códigos de recuperação (API da conta do Argos). */
574
+ mfa: 'mfa',
575
+ /** Sessão atual, token de acesso, tokens de aplicação e sessões e dispositivos. */
576
+ sessions: 'sessoes',
577
+ /** @deprecated A seção "Conta e segurança" virou "MFA" e "Sessões": `seguranca` abre a de MFA (ver
578
+ * `SPINE_SETTINGS_ALIASES`). Use `mfa` ou `sessions`. */
483
579
  security: 'seguranca',
484
580
  about: 'sobre',
485
581
  };
582
+ /** Chaves antigas de `?settings=` que continuam abrindo a seção que as substituiu (links já publicados). */
583
+ const SPINE_SETTINGS_ALIASES = {
584
+ [SPINE_SETTINGS_KEYS.security]: SPINE_SETTINGS_KEYS.mfa,
585
+ };
586
+ /** Chave de seção que vale hoje: troca uma chave antiga pela nova; as demais passam iguais. */
587
+ function resolveSettingsSection(section) {
588
+ // hasOwn: `?settings=constructor` não pode devolver o que vem do protótipo.
589
+ return Object.hasOwn(SPINE_SETTINGS_ALIASES, section) ? SPINE_SETTINGS_ALIASES[section] : section;
590
+ }
486
591
  const SPINE_SETTINGS_SECTIONS = new InjectionToken('SPINE_SETTINGS_SECTIONS');
487
592
  /** Registra uma seção do app no painel de configurações (aparece depois das do spine, antes de
488
593
  * "Sobre"). Pode ser chamado várias vezes, uma por seção. */
@@ -496,8 +601,9 @@ class SpineSettingsService {
496
601
  /** Seção pedida na abertura (vazio = a primeira). */
497
602
  this.section = signal('', ...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
498
603
  }
604
+ /** `section` aceita também as chaves antigas (`seguranca` abre "MFA"). */
499
605
  open(section = '') {
500
- this.section.set(section);
606
+ this.section.set(resolveSettingsSection(section));
501
607
  this.isOpen.set(true);
502
608
  }
503
609
  close() {
@@ -698,6 +804,7 @@ class HeaderComponent {
698
804
  this.settings = inject(SpineSettingsService);
699
805
  this.news = inject(SpineNewsService);
700
806
  this.userStorage = inject(SpineUserStorage);
807
+ this.logoutService = inject(SpineLogoutService);
701
808
  this.quickQuery = '';
702
809
  this.quickOpen = false;
703
810
  this.quickResults = [];
@@ -708,6 +815,10 @@ class HeaderComponent {
708
815
  /** Verdadeiro quando o painel está exibindo o histórico (campo vazio). */
709
816
  this.showingRecent = false;
710
817
  this.appName = config?.appName ?? 'Hermes';
818
+ // Lista apagada fora daqui (logout, Configurações › Dados do navegador) ou outra conta: relê, para a
819
+ // próxima busca não ressuscitar o que foi apagado.
820
+ const stop = this.userStorage.onChange(() => (this.recentSearches = this.loadRecent()));
821
+ inject(DestroyRef).onDestroy(stop);
711
822
  }
712
823
  async ngOnInit() {
713
824
  this.updateView(this.viewService.currentView);
@@ -776,9 +887,9 @@ class HeaderComponent {
776
887
  async switchBadge(badgeTypeId) {
777
888
  await this.authService.switchBadge(badgeTypeId);
778
889
  }
890
+ /** Sai pelo `SpineLogoutService`: com falha no Argos a pessoa fica aqui, com aviso, e o histórico fica. */
779
891
  logout() {
780
- this.userStorage.clearHistory();
781
- this.authService.logout();
892
+ return this.logoutService.logout();
782
893
  }
783
894
  /** Reabre o tour guiado de onboarding. */
784
895
  startTour() { this.onboarding.restart(); }
@@ -805,7 +916,8 @@ class HeaderComponent {
805
916
  // Só quando o app registrou alguma novidade vigente (provideSpineNews).
806
917
  if (this.news.hasAny())
807
918
  items.push({ id: 'news', label: this.text.news, icon: 'star' });
808
- items.push({ id: 'logout', label: this.text.logout, icon: 'logout', danger: true, separatorAbove: true });
919
+ // Desabilitado só enquanto a saída está em andamento; volta sozinho se ela falhar.
920
+ items.push({ id: 'logout', label: this.text.logout, icon: 'logout', danger: true, separatorAbove: true, disabled: this.logoutService.pending() });
809
921
  return items;
810
922
  }
811
923
  onProfileMenuChosen(id) {
@@ -831,7 +943,7 @@ class HeaderComponent {
831
943
  this.settings.open(SPINE_SETTINGS_KEYS.appearance);
832
944
  return;
833
945
  case 'logout':
834
- this.logout();
946
+ void this.logout();
835
947
  return;
836
948
  }
837
949
  }
@@ -1289,7 +1401,8 @@ class OnboardingOverlayComponent {
1289
1401
  const rect = target.getBoundingClientRect();
1290
1402
  if (rect.top >= 0 && rect.bottom <= window.innerHeight)
1291
1403
  return;
1292
- const calm = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1404
+ // Escolha do app (Configurações › Acessibilidade) ou, sem ela, o sistema.
1405
+ const calm = prefersReducedMotion();
1293
1406
  target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: calm ? 'auto' : 'smooth' });
1294
1407
  }
1295
1408
  /** Largura que o CSS dá ao balão (medida quando já existe) e a altura informada. */
@@ -1396,10 +1509,14 @@ function buildNewsIllustration(illustration, palette) {
1396
1509
  const y = round(CY + 104 * Math.sin(angle));
1397
1510
  parts.push(`<g transform="translate(${x} ${y})"><g class="sat" style="animation-delay:${-(i * 1.3).toFixed(1)}s">`, `<circle r="28" fill="${c.badge}" stroke="${c.border}" stroke-width="2"/>`, icon(sat.icon, -14, -14, 28, palette.icon), `<text y="48" text-anchor="middle" font-size="15" fill="${c.label}">${xml(sat.label)}</text>`, `</g></g>`);
1398
1511
  });
1399
- const style = orbiting
1512
+ // A imagem é um data URI: o `data-motion` do app (Configurações › Acessibilidade) não chega lá dentro.
1513
+ // Com escolha no app, ela decide aqui, ao gerar; sem escolha, a media query da própria imagem segue o sistema.
1514
+ const motion = typeof document === 'undefined' ? 'system' : motionPreferenceOf(document.documentElement);
1515
+ const style = orbiting && motion !== 'reduce'
1400
1516
  ? '<style>.sat{animation:drift 5.6s ease-in-out infinite alternate}' +
1401
1517
  '@keyframes drift{from{transform:translateY(-5px)}to{transform:translateY(5px)}}' +
1402
- '@media (prefers-reduced-motion:reduce){.sat{animation:none}}</style>'
1518
+ (motion === 'full' ? '' : '@media (prefers-reduced-motion:reduce){.sat{animation:none}}') +
1519
+ '</style>'
1403
1520
  : '';
1404
1521
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${WIDTH} ${HEIGHT}" width="${WIDTH}" height="${HEIGHT}" ` +
1405
1522
  `font-family="${xml(palette.font)}">${style}${parts.join('')}</svg>`;
@@ -1565,6 +1682,32 @@ class SpineAccountSecurityService {
1565
1682
  endOtherSessions() {
1566
1683
  return this.send('DELETE', '/sessions/others');
1567
1684
  }
1685
+ /** Entradas e tentativas desta conta nos últimos 90 dias, da mais recente. `result` filtra só sucessos ou falhas. */
1686
+ activity(params = {}) {
1687
+ const query = new URLSearchParams();
1688
+ if (params.limit)
1689
+ query.set('limit', String(params.limit));
1690
+ if (params.offset)
1691
+ query.set('offset', String(params.offset));
1692
+ if (params.result)
1693
+ query.set('result', params.result);
1694
+ const qs = query.toString();
1695
+ return this.send('GET', `/activity${qs ? `?${qs}` : ''}`);
1696
+ }
1697
+ badges() {
1698
+ return this.send('GET', '/badges');
1699
+ }
1700
+ /** Crachá com que o login entra nesta aplicação (`null` volta ao padrão: o de menor nível). */
1701
+ setDefaultBadge(badgeId) {
1702
+ return this.send('PUT', '/badges/default', { badge_id: badgeId });
1703
+ }
1704
+ preferences() {
1705
+ return this.send('GET', '/preferences');
1706
+ }
1707
+ /** Substitui o documento de preferências desta aplicação na conta (até 16 KB). */
1708
+ savePreferences(preferences) {
1709
+ return this.send('PUT', '/preferences', { preferences });
1710
+ }
1568
1711
  async send(method, path, body) {
1569
1712
  try {
1570
1713
  return await this.request(method, path, body);
@@ -1597,6 +1740,778 @@ function accountErrorCode(err) {
1597
1740
  return typeof code === 'string' ? code : err.status === 0 ? 'network' : '';
1598
1741
  }
1599
1742
 
1743
+ /** Versão do documento guardado na conta (muda só se o formato deixar de ser compatível). */
1744
+ const DOC_VERSION = 1;
1745
+ /** Espera depois da última mudança antes de gravar. */
1746
+ const SPINE_PREFERENCES_SAVE_DELAY_MS = 1500;
1747
+ /** Favoritos guardados na conta (o servidor aceita até 16 KB no documento inteiro). */
1748
+ const MAX_FAVORITES = 40;
1749
+ /** Quanto o login espera a conta responder antes de seguir com o que está no navegador. */
1750
+ const READY_TIMEOUT_MS = 2000;
1751
+ /**
1752
+ * Preferências na conta (`SpineFeatures.accountPreferences`): espelha no Argos (`GET/PUT {api}/preferences`, por
1753
+ * usuário e aplicação) o que o painel de configurações guarda no navegador — tema, cor, densidade, fonte, menu
1754
+ * lateral, idioma, fuso, relógio, primeiro dia da semana, tamanho do texto, animações, atalhos de tecla única,
1755
+ * inatividade, tela inicial, tipos de aviso e favoritos. As telas Aparência, Região etc. não mudam: o
1756
+ * armazenamento local continua sendo a fonte de cada tela, e este serviço só sincroniza.
1757
+ *
1758
+ * - No início da sessão (`start()`, chamado pelo layout e pelo `/callback`), lê a conta e aplica o que estiver lá
1759
+ * (pelos mesmos `set*` das telas); favoritos são somados (os da conta primeiro). Conta vazia recebe o que está no
1760
+ * navegador.
1761
+ * - Depois, cada mudança é gravada na conta, agrupada (`SPINE_PREFERENCES_SAVE_DELAY_MS`).
1762
+ * - Sem a API da conta, com o recurso desligado ou com um Argos que não tem o endpoint (404/400), fica só no
1763
+ * navegador (`status() === 'local'`), sem erro na tela.
1764
+ */
1765
+ class SpineAccountPreferencesService {
1766
+ constructor() {
1767
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
1768
+ this.api = inject(SpineAccountSecurityService);
1769
+ this.preferences = inject(SpinePreferencesService);
1770
+ this.quickAccess = inject(SpineQuickAccessService);
1771
+ this.hermesTheme = inject(HermesThemeService);
1772
+ this.themeConfig = inject(HERMES_THEME_CONFIG);
1773
+ this.theme = inject(ThemeService);
1774
+ this.density = inject(DensityService);
1775
+ this.presets = inject(ThemePresetService);
1776
+ this.sidebar = inject(HermesSidebarService);
1777
+ /** Recurso ligado e API da conta disponível. */
1778
+ this.enabled = !!inject(SPINE_CONFIG, { optional: true })?.features?.accountPreferences && this.api.available;
1779
+ this.status = signal(this.enabled ? 'idle' : 'local', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1780
+ /** Hora da última leitura/gravação bem-sucedida. */
1781
+ this.syncedAt = signal(null, ...(ngDevMode ? [{ debugName: "syncedAt" }] : /* istanbul ignore next */ []));
1782
+ /** O documento que o navegador tem agora. */
1783
+ this.snapshot = computed(() => {
1784
+ const p = this.preferences;
1785
+ return {
1786
+ v: DOC_VERSION,
1787
+ themeMode: this.hermesTheme.mode(),
1788
+ family: this.hermesTheme.family(),
1789
+ density: this.hermesTheme.density(),
1790
+ font: this.hermesTheme.font(),
1791
+ sidebar: this.sidebar.mode(),
1792
+ language: p.language(),
1793
+ timeZone: p.timeZone(),
1794
+ textScale: p.textScale(),
1795
+ motion: p.motion(),
1796
+ hourCycle: p.hourCycle(),
1797
+ weekStart: p.weekStart(),
1798
+ characterKeys: p.characterKeys(),
1799
+ idleMinutes: p.idleMinutes(),
1800
+ startPage: p.startPage(),
1801
+ notices: { ...p.noticeTypes() },
1802
+ browserNotices: p.browserNotices(),
1803
+ favorites: this.quickAccess.favorites().slice(0, MAX_FAVORITES).map((f) => ({ ...f })),
1804
+ };
1805
+ }, ...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
1806
+ this.loaded = false;
1807
+ this.lastSaved = '';
1808
+ this.timer = null;
1809
+ this.readyPromise = null;
1810
+ if (!this.enabled || !this.browser)
1811
+ return;
1812
+ effect(() => {
1813
+ const json = stable(this.snapshot());
1814
+ if (!this.loaded || json === this.lastSaved)
1815
+ return;
1816
+ untracked(() => this.schedule());
1817
+ });
1818
+ inject(DestroyRef).onDestroy(() => this.clearTimer());
1819
+ }
1820
+ /**
1821
+ * Lê a conta e aplica (uma vez por sessão do app; chamadas seguintes devolvem a mesma promessa). Nunca rejeita.
1822
+ */
1823
+ start() {
1824
+ if (!this.enabled || !this.browser)
1825
+ return Promise.resolve();
1826
+ this.readyPromise ??= this.load();
1827
+ return this.readyPromise;
1828
+ }
1829
+ /** Como `start()`, mas sem esperar mais que `timeoutMs` (o login não fica parado se o Argos demorar). */
1830
+ ready(timeoutMs = READY_TIMEOUT_MS) {
1831
+ return Promise.race([this.start(), new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
1832
+ }
1833
+ /** Grava agora o que estiver pendente ("Sincronizar agora"); relê a conta se ela ainda não respondeu. */
1834
+ async syncNow() {
1835
+ if (!this.enabled)
1836
+ return;
1837
+ if (!this.loaded) {
1838
+ this.readyPromise = this.load();
1839
+ await this.readyPromise;
1840
+ return;
1841
+ }
1842
+ this.clearTimer();
1843
+ await this.save();
1844
+ }
1845
+ async load() {
1846
+ this.status.set('loading');
1847
+ try {
1848
+ const { preferences } = await this.api.preferences();
1849
+ const remote = isDoc(preferences) ? preferences : null;
1850
+ if (remote)
1851
+ this.apply(remote);
1852
+ this.loaded = true;
1853
+ this.syncedAt.set(new Date());
1854
+ if (remote) {
1855
+ this.lastSaved = stable(remote);
1856
+ // A conta pode ter menos do que o navegador (favoritos somados, chave nova): grava a diferença.
1857
+ if (stable(this.snapshot()) !== this.lastSaved)
1858
+ this.schedule();
1859
+ else
1860
+ this.status.set('synced');
1861
+ }
1862
+ else {
1863
+ await this.save();
1864
+ }
1865
+ }
1866
+ catch (err) {
1867
+ this.failed(err);
1868
+ }
1869
+ }
1870
+ /** Aplica o documento da conta pelos mesmos caminhos das telas (cada `set*` também grava no navegador). */
1871
+ apply(doc) {
1872
+ const p = this.preferences;
1873
+ if (doc.themeMode && doc.themeMode !== this.hermesTheme.mode())
1874
+ this.theme.setMode(doc.themeMode);
1875
+ if (doc.family && doc.family !== this.hermesTheme.family()) {
1876
+ const family = this.presets.families.find((f) => f.id === (this.themeConfig.aliases?.[doc.family] ?? doc.family));
1877
+ if (family)
1878
+ this.presets.setPreset(family);
1879
+ }
1880
+ if (doc.density && doc.density !== this.hermesTheme.density())
1881
+ this.density.setDensity(doc.density);
1882
+ if (doc.font && doc.font !== this.hermesTheme.font()) {
1883
+ const fonts = this.hermesTheme.fonts ?? [];
1884
+ if (!fonts.length || fonts.some((f) => f.id === doc.font))
1885
+ this.hermesTheme.setFont(doc.font);
1886
+ }
1887
+ if (doc.sidebar && doc.sidebar !== this.sidebar.mode())
1888
+ this.sidebar.setMode(doc.sidebar);
1889
+ if (typeof doc.language === 'string' && doc.language !== p.language())
1890
+ p.setLanguage(doc.language);
1891
+ if (typeof doc.timeZone === 'string' && doc.timeZone !== p.timeZone())
1892
+ p.setTimeZone(doc.timeZone);
1893
+ if (typeof doc.textScale === 'number' && doc.textScale !== p.textScale())
1894
+ p.setTextScale(doc.textScale);
1895
+ if (doc.motion && doc.motion !== p.motion())
1896
+ p.setMotion(doc.motion);
1897
+ if (doc.hourCycle !== undefined && doc.hourCycle !== p.hourCycle())
1898
+ p.setHourCycle(doc.hourCycle);
1899
+ if (doc.weekStart !== undefined && doc.weekStart !== p.weekStart())
1900
+ p.setWeekStart(doc.weekStart);
1901
+ if (typeof doc.characterKeys === 'boolean' && doc.characterKeys !== p.characterKeys())
1902
+ p.setCharacterKeys(doc.characterKeys);
1903
+ if (typeof doc.idleMinutes === 'number' && doc.idleMinutes !== p.idleMinutes())
1904
+ p.setIdleMinutes(doc.idleMinutes);
1905
+ if (typeof doc.startPage === 'string' && doc.startPage !== p.startPage())
1906
+ p.setStartPage(doc.startPage);
1907
+ if (doc.notices && typeof doc.notices === 'object')
1908
+ p.setNoticeTypes({ ...p.noticeTypes(), ...doc.notices });
1909
+ // O aviso do navegador depende da permissão DESTE navegador: a conta só desliga, nunca liga sozinha.
1910
+ if (doc.browserNotices === false && p.browserNotices())
1911
+ p.setBrowserNotices(false);
1912
+ if (Array.isArray(doc.favorites)) {
1913
+ const remote = doc.favorites;
1914
+ const ids = new Set(remote.map((f) => f?.id));
1915
+ const localOnly = this.quickAccess.favorites().filter((f) => !ids.has(f.id));
1916
+ this.quickAccess.setFavorites([...remote, ...localOnly]);
1917
+ }
1918
+ }
1919
+ schedule() {
1920
+ this.clearTimer();
1921
+ this.status.set('saving');
1922
+ this.timer = setTimeout(() => void this.save(), SPINE_PREFERENCES_SAVE_DELAY_MS);
1923
+ }
1924
+ async save() {
1925
+ this.timer = null;
1926
+ const doc = this.snapshot();
1927
+ const json = stable(doc);
1928
+ this.status.set('saving');
1929
+ try {
1930
+ await this.api.savePreferences(doc);
1931
+ this.lastSaved = json;
1932
+ this.syncedAt.set(new Date());
1933
+ this.status.set('synced');
1934
+ }
1935
+ catch (err) {
1936
+ this.failed(err);
1937
+ }
1938
+ }
1939
+ failed(err) {
1940
+ // Argos sem o endpoint (versão anterior) ou sem client no token: fica só no navegador, sem insistir.
1941
+ if (err instanceof HttpErrorResponse && (err.status === 404 || err.status === 400 || err.status === 405)) {
1942
+ this.status.set('local');
1943
+ this.loaded = false;
1944
+ return;
1945
+ }
1946
+ this.status.set('error');
1947
+ }
1948
+ clearTimer() {
1949
+ if (this.timer)
1950
+ clearTimeout(this.timer);
1951
+ this.timer = null;
1952
+ }
1953
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAccountPreferencesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1954
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAccountPreferencesService, providedIn: 'root' }); }
1955
+ }
1956
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAccountPreferencesService, decorators: [{
1957
+ type: Injectable,
1958
+ args: [{ providedIn: 'root' }]
1959
+ }], ctorParameters: () => [] });
1960
+ /** JSON com as chaves dos objetos em ordem (a conta pode devolver outra ordem). */
1961
+ function stable(value) {
1962
+ return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
1963
+ ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)))
1964
+ : v);
1965
+ }
1966
+ function isDoc(value) {
1967
+ return !!value && typeof value === 'object' && !Array.isArray(value) && typeof value.v === 'number';
1968
+ }
1969
+
1970
+ const positive = (value) => {
1971
+ const n = Number(value);
1972
+ return Number.isFinite(n) && n > 0 ? Math.round(n) : 0;
1973
+ };
1974
+ /** Opções e limites de inatividade do app. Sem o recurso, `enabled` é falso e não há opções. */
1975
+ function idleLimits(features) {
1976
+ const config = features?.idleTimeout;
1977
+ if (!config)
1978
+ return { enabled: false, min: 0, max: 0, options: [] };
1979
+ const min = typeof config === 'object' ? positive(config.min) : 0;
1980
+ let max = typeof config === 'object' ? positive(config.max) : 0;
1981
+ if (max && min && max < min)
1982
+ max = min;
1983
+ const options = SPINE_IDLE_OPTIONS.filter((m) => (m === 0 ? !max : m >= min && (!max || m <= max)));
1984
+ // Um limite fora das opções padrão (ex.: máximo de 10 min) entra como opção própria.
1985
+ const extra = [min, max].filter((m) => m > 0 && !options.includes(m));
1986
+ return { enabled: true, min, max, options: [...options, ...extra].sort((a, b) => a - b) };
1987
+ }
1988
+ /** Minutos que valem de fato: a escolha da pessoa dentro dos limites do app (0 = não encerra). */
1989
+ function effectiveIdleMinutes(chosen, limits) {
1990
+ if (!limits.enabled)
1991
+ return 0;
1992
+ let minutes = chosen > 0 ? chosen : 0;
1993
+ if (limits.max && (minutes === 0 || minutes > limits.max))
1994
+ minutes = limits.max;
1995
+ if (limits.min && minutes > 0 && minutes < limits.min)
1996
+ minutes = limits.min;
1997
+ return minutes;
1998
+ }
1999
+
2000
+ /** Segundos de aviso antes de encerrar. */
2001
+ const SPINE_IDLE_WARNING_SECONDS = 60;
2002
+ /** Intervalo entre gravações da última atividade no `localStorage` (as outras abas leem de lá). */
2003
+ const WRITE_EVERY_MS = 5_000;
2004
+ const ACTIVITY_EVENTS = ['pointerdown', 'pointermove', 'keydown', 'wheel', 'touchstart', 'scroll'];
2005
+ /**
2006
+ * Encerrar por inatividade (`SpineFeatures.idleTimeout`): conta o tempo sem uso (ponteiro, teclado, rolagem) em
2007
+ * TODAS as abas do app — a última atividade vai para o `localStorage` (`hermes_<clientId>_last_activity`) e cada aba
2008
+ * confere a mais recente —, abre o aviso com contagem regressiva `SPINE_IDLE_WARNING_SECONDS` antes do fim e, se
2009
+ * ninguém responder, sai pelo `SpineLogoutService` (o mesmo "Sair" do menu). Com o aviso aberto, só "Continuar
2010
+ * conectado" (ou atividade em outra aba) mantém a sessão: mexer o ponteiro sobre o aviso não conta.
2011
+ *
2012
+ * Os minutos vêm do `SpinePreferencesService.idleMinutes`, limitados pelo app (`effectiveIdleMinutes`). Ligado pelo
2013
+ * `LayoutComponent` (só existe com sessão); não faz nada fora do navegador.
2014
+ */
2015
+ class SpineIdleService {
2016
+ constructor() {
2017
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
2018
+ this.zone = inject(NgZone);
2019
+ this.preferences = inject(SpinePreferencesService);
2020
+ this.logoutService = inject(SpineLogoutService);
2021
+ this.config = inject(SPINE_CONFIG, { optional: true });
2022
+ this.storageKey = `hermes_${this.config?.clientId || 'app'}_last_activity`;
2023
+ this.limits = idleLimits(this.config?.features);
2024
+ /** Minutos que valem agora (0 = não encerra). */
2025
+ this.minutes = computed(() => effectiveIdleMinutes(this.preferences.idleMinutes(), this.limits), ...(ngDevMode ? [{ debugName: "minutes" }] : /* istanbul ignore next */ []));
2026
+ /** Aviso aberto. */
2027
+ this.warning = signal(false, ...(ngDevMode ? [{ debugName: "warning" }] : /* istanbul ignore next */ []));
2028
+ /** Segundos que faltam, enquanto o aviso está aberto. */
2029
+ this.secondsLeft = signal(SPINE_IDLE_WARNING_SECONDS, ...(ngDevMode ? [{ debugName: "secondsLeft" }] : /* istanbul ignore next */ []));
2030
+ this.lastActivity = Date.now();
2031
+ this.lastWrite = 0;
2032
+ this.timer = null;
2033
+ this.expiring = false;
2034
+ /** Ligado por `start()` (o layout, que só existe com sessão). */
2035
+ this.active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
2036
+ this.onActivity = () => {
2037
+ if (this.warning())
2038
+ return;
2039
+ this.touch();
2040
+ };
2041
+ if (!this.browser)
2042
+ return;
2043
+ effect(() => {
2044
+ if (this.active() && this.minutes() > 0)
2045
+ this.run();
2046
+ else
2047
+ this.stop();
2048
+ });
2049
+ inject(DestroyRef).onDestroy(() => this.stop());
2050
+ }
2051
+ /** Liga a contagem (idempotente). Para sozinha quando a escolha vira "Nunca". */
2052
+ start() {
2053
+ this.active.set(true);
2054
+ }
2055
+ /** "Continuar conectado": fecha o aviso e recomeça a contagem. */
2056
+ stay() {
2057
+ this.warning.set(false);
2058
+ this.touch(true);
2059
+ }
2060
+ /** "Sair agora". */
2061
+ logoutNow() {
2062
+ void this.expire();
2063
+ }
2064
+ /** Conta uma atividade agora (e a grava para as outras abas no máximo a cada 5 s, ou já, com `force`). */
2065
+ touch(force = false) {
2066
+ const now = Date.now();
2067
+ this.lastActivity = now;
2068
+ if (!force && now - this.lastWrite < WRITE_EVERY_MS)
2069
+ return;
2070
+ this.lastWrite = now;
2071
+ try {
2072
+ localStorage.setItem(this.storageKey, String(now));
2073
+ }
2074
+ catch { /* storage indisponível */ }
2075
+ }
2076
+ /** Confere o tempo sem uso: abre o aviso, atualiza a contagem ou encerra. Exposto para os testes. */
2077
+ check(now = Date.now()) {
2078
+ const minutes = this.minutes();
2079
+ if (!minutes || this.expiring)
2080
+ return;
2081
+ const idle = now - Math.max(this.lastActivity, this.storedActivity());
2082
+ const total = minutes * 60_000;
2083
+ const warnAt = Math.max(0, total - SPINE_IDLE_WARNING_SECONDS * 1000);
2084
+ if (idle >= total) {
2085
+ void this.expire();
2086
+ }
2087
+ else if (idle >= warnAt) {
2088
+ this.zone.run(() => {
2089
+ this.secondsLeft.set(Math.max(1, Math.ceil((total - idle) / 1000)));
2090
+ this.warning.set(true);
2091
+ });
2092
+ }
2093
+ else if (this.warning()) {
2094
+ // Atividade em outra aba: o aviso some sozinho.
2095
+ this.zone.run(() => this.warning.set(false));
2096
+ }
2097
+ }
2098
+ storedActivity() {
2099
+ try {
2100
+ const value = Number(localStorage.getItem(this.storageKey));
2101
+ return Number.isFinite(value) ? value : 0;
2102
+ }
2103
+ catch {
2104
+ return 0;
2105
+ }
2106
+ }
2107
+ run() {
2108
+ if (this.timer)
2109
+ return;
2110
+ this.touch(true);
2111
+ this.zone.runOutsideAngular(() => {
2112
+ for (const name of ACTIVITY_EVENTS)
2113
+ document.addEventListener(name, this.onActivity, { passive: true, capture: true });
2114
+ this.timer = setInterval(() => this.check(), 1000);
2115
+ });
2116
+ }
2117
+ stop() {
2118
+ if (this.timer)
2119
+ clearInterval(this.timer);
2120
+ this.timer = null;
2121
+ if (this.browser)
2122
+ for (const name of ACTIVITY_EVENTS)
2123
+ document.removeEventListener(name, this.onActivity, { capture: true });
2124
+ this.warning.set(false);
2125
+ }
2126
+ async expire() {
2127
+ if (this.expiring)
2128
+ return;
2129
+ this.expiring = true;
2130
+ this.zone.run(() => this.warning.set(false));
2131
+ try {
2132
+ const ok = await this.logoutService.logout();
2133
+ // Saída recusada pelo Argos: a sessão continua (o aviso de falha já apareceu); recomeça a contagem.
2134
+ if (!ok)
2135
+ this.touch(true);
2136
+ }
2137
+ finally {
2138
+ this.expiring = false;
2139
+ }
2140
+ }
2141
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineIdleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2142
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineIdleService, providedIn: 'root' }); }
2143
+ }
2144
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineIdleService, decorators: [{
2145
+ type: Injectable,
2146
+ args: [{ providedIn: 'root' }]
2147
+ }], ctorParameters: () => [] });
2148
+
2149
+ /**
2150
+ * Avisos do app (`SpineFeatures.notices`): o app declara os tipos que emite (`{ id, label }`) e chama
2151
+ * `notify(tipo, aviso)` de onde já sabe do acontecimento (subscription, polling). Cada tipo pode ser desligado em
2152
+ * Configurações › Notificações; tipo desligado não aparece. O aviso ligado:
2153
+ *
2154
+ * - aparece na tela (`HermesToastService`, com "Abrir" quando tem `url`);
2155
+ * - com a aba em segundo plano, o aviso do navegador ligado e a permissão concedida, vira também um aviso do sistema
2156
+ * (Notification API); clicar nele traz a aba para a frente e abre a `url`.
2157
+ *
2158
+ * Tipo não declarado em `SpineFeatures.notices.types` aparece sempre (em desenvolvimento, com um aviso no console).
2159
+ */
2160
+ class SpineNoticesService {
2161
+ constructor() {
2162
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
2163
+ this.zone = inject(NgZone);
2164
+ this.injector = inject(Injector);
2165
+ this.toast = inject(HermesToastService);
2166
+ this.preferences = inject(SpinePreferencesService);
2167
+ this.messages = inject(SPINE_MESSAGES);
2168
+ this.config = inject(SPINE_CONFIG, { optional: true })?.features?.notices;
2169
+ this.warned = new Set();
2170
+ /** Recurso ligado pelo app. */
2171
+ this.enabled = !!this.config;
2172
+ /** Tipos declarados pelo app. */
2173
+ this.types = this.config?.types ?? [];
2174
+ /** Permissão atual do navegador. */
2175
+ this.permission = signal(this.readPermission(), ...(ngDevMode ? [{ debugName: "permission" }] : /* istanbul ignore next */ []));
2176
+ /** Aviso do navegador valendo de fato: ligado nas configurações e permitido pelo navegador. */
2177
+ this.browserActive = computed(() => this.preferences.browserNotices() && this.permission() === 'granted', ...(ngDevMode ? [{ debugName: "browserActive" }] : /* istanbul ignore next */ []));
2178
+ }
2179
+ /** O tipo está ligado (escolha da pessoa ou, sem escolha, o `defaultEnabled` dele). */
2180
+ isEnabled(typeId) {
2181
+ const chosen = this.preferences.noticeTypes()[typeId];
2182
+ if (typeof chosen === 'boolean')
2183
+ return chosen;
2184
+ return this.types.find((t) => t.id === typeId)?.defaultEnabled ?? true;
2185
+ }
2186
+ setEnabled(typeId, enabled) {
2187
+ this.preferences.setNoticeType(typeId, enabled);
2188
+ }
2189
+ /**
2190
+ * Liga o aviso do navegador: pede a permissão quando ainda não foi pedida. Devolve se ficou ligado (permissão
2191
+ * negada ou navegador sem suporte: continua desligado).
2192
+ */
2193
+ async enableBrowser() {
2194
+ if (this.readPermission() === 'unsupported') {
2195
+ this.permission.set('unsupported');
2196
+ return false;
2197
+ }
2198
+ let permission = Notification.permission;
2199
+ if (permission === 'default') {
2200
+ try {
2201
+ permission = await Notification.requestPermission();
2202
+ }
2203
+ catch {
2204
+ permission = 'denied';
2205
+ }
2206
+ }
2207
+ this.permission.set(permission);
2208
+ const granted = permission === 'granted';
2209
+ this.preferences.setBrowserNotices(granted);
2210
+ return granted;
2211
+ }
2212
+ disableBrowser() {
2213
+ this.preferences.setBrowserNotices(false);
2214
+ }
2215
+ /** Relê a permissão (a pessoa pode ter mudado nas configurações do navegador). */
2216
+ refreshPermission() {
2217
+ this.permission.set(this.readPermission());
2218
+ }
2219
+ /** Mostra o aviso, se o tipo estiver ligado. Devolve se mostrou. */
2220
+ notify(typeId, notice) {
2221
+ if (this.types.length && !this.types.some((t) => t.id === typeId) && isDevMode() && !this.warned.has(typeId)) {
2222
+ this.warned.add(typeId);
2223
+ console.warn(`[hermes-spine] aviso de tipo não declarado em SpineFeatures.notices.types: ${typeId}`);
2224
+ }
2225
+ if (!this.isEnabled(typeId))
2226
+ return false;
2227
+ const url = notice.url && notice.url.startsWith('/') && !notice.url.startsWith('//') ? notice.url : undefined;
2228
+ const message = notice.body ? `${notice.title} — ${notice.body}` : notice.title;
2229
+ this.toast.push(notice.intent ?? 'info', message, url
2230
+ ? { action: { label: this.messages.accountFeatures.noticeOpen, run: () => this.open(url) } }
2231
+ : undefined);
2232
+ if (this.browser && document.visibilityState === 'hidden' && this.preferences.browserNotices())
2233
+ this.system(typeId, notice, url);
2234
+ return true;
2235
+ }
2236
+ system(typeId, notice, url) {
2237
+ this.refreshPermission();
2238
+ if (this.permission() !== 'granted')
2239
+ return;
2240
+ try {
2241
+ const shown = new Notification(notice.title, { body: notice.body, tag: url ? `${typeId}:${url}` : typeId });
2242
+ shown.onclick = () => {
2243
+ window.focus();
2244
+ shown.close();
2245
+ if (url)
2246
+ this.open(url);
2247
+ };
2248
+ }
2249
+ catch {
2250
+ // Alguns navegadores (Chrome no Android) só aceitam aviso por service worker: fica só o da tela.
2251
+ }
2252
+ }
2253
+ open(url) {
2254
+ this.zone.run(() => void this.injector.get(Router).navigateByUrl(url));
2255
+ }
2256
+ readPermission() {
2257
+ if (!this.browser || typeof Notification === 'undefined')
2258
+ return 'unsupported';
2259
+ return Notification.permission;
2260
+ }
2261
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineNoticesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2262
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineNoticesService, providedIn: 'root' }); }
2263
+ }
2264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineNoticesService, decorators: [{
2265
+ type: Injectable,
2266
+ args: [{ providedIn: 'root' }]
2267
+ }] });
2268
+
2269
+ /** Rotas do shell que nunca viram "última tela". */
2270
+ const IGNORED = /^\/(callback|logoff|loggedout|login|no-permission|blocked)(\/|$)/;
2271
+ /** Caminho interno seguro (`/x`, nunca `//host` nem `http:`). */
2272
+ function isInternalPath(url) {
2273
+ return typeof url === 'string' && url.startsWith('/') && !url.startsWith('//') && !url.includes('\\');
2274
+ }
2275
+ /**
2276
+ * Tela inicial (`SpineFeatures.startPage`): para onde o app vai depois do login quando a pessoa não pediu uma URL
2277
+ * (`/callback` sem URL de retorno, ou com `/` ou `/home`) — o início do app, um favorito ou a última tela visitada.
2278
+ *
2279
+ * A última tela fica no `SpineUserStorage` (`lastPage`, por usuário) e só é gravada enquanto a escolha for
2280
+ * "Última tela visitada" (`track()`, ligado pelo `LayoutComponent`); ao contrário dos recentes, ela fica no logout.
2281
+ */
2282
+ class SpineStartPageService {
2283
+ constructor() {
2284
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
2285
+ this.injector = inject(Injector);
2286
+ this.destroyRef = inject(DestroyRef);
2287
+ this.preferences = inject(SpinePreferencesService);
2288
+ this.storage = inject(SpineUserStorage);
2289
+ this.enabled = !!inject(SPINE_CONFIG, { optional: true })?.features?.startPage;
2290
+ this.tracking = false;
2291
+ }
2292
+ /** Grava a URL de cada navegação enquanto a escolha for "Última tela visitada" (idempotente). */
2293
+ track() {
2294
+ if (!this.enabled || !this.browser || this.tracking)
2295
+ return;
2296
+ this.tracking = true;
2297
+ const router = this.injector.get(Router);
2298
+ const sub = router.events
2299
+ .pipe(filter((e) => e instanceof NavigationEnd))
2300
+ .subscribe((e) => this.remember(e.urlAfterRedirects));
2301
+ this.destroyRef.onDestroy(() => sub.unsubscribe());
2302
+ }
2303
+ /** Guarda `url` como última tela (só com a escolha "Última tela visitada"). */
2304
+ remember(url) {
2305
+ if (!this.enabled || this.preferences.startPage() !== 'last' || !isInternalPath(url) || IGNORED.test(url))
2306
+ return;
2307
+ try {
2308
+ localStorage.setItem(this.storage.key(SPINE_USER_STORAGE_KEYS.lastPage), url);
2309
+ }
2310
+ catch { /* storage indisponível */ }
2311
+ }
2312
+ /** Destino da tela inicial escolhida, ou `null` (início do app, recurso desligado ou favorito que sumiu). */
2313
+ target() {
2314
+ if (!this.enabled || !this.browser)
2315
+ return null;
2316
+ const choice = this.preferences.startPage();
2317
+ if (choice === 'last') {
2318
+ let url = null;
2319
+ try {
2320
+ url = localStorage.getItem(this.storage.key(SPINE_USER_STORAGE_KEYS.lastPage));
2321
+ }
2322
+ catch {
2323
+ url = null;
2324
+ }
2325
+ return isInternalPath(url) && !IGNORED.test(url) ? url : null;
2326
+ }
2327
+ if (choice.startsWith('fav:')) {
2328
+ const id = choice.slice(4);
2329
+ const favorite = this.injector.get(SpineQuickAccessService).favorites().find((f) => f.id === id);
2330
+ return favorite && isInternalPath(favorite.redirect) ? favorite.redirect : null;
2331
+ }
2332
+ return null;
2333
+ }
2334
+ /** `true` quando o login não pediu uma URL própria (aí a tela inicial manda). */
2335
+ static isDefaultReturn(returnUrl) {
2336
+ return !returnUrl || returnUrl === '/' || returnUrl === '/home';
2337
+ }
2338
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineStartPageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2339
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineStartPageService, providedIn: 'root' }); }
2340
+ }
2341
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineStartPageService, decorators: [{
2342
+ type: Injectable,
2343
+ args: [{ providedIn: 'root' }]
2344
+ }] });
2345
+
2346
+ /** Itens por página. */
2347
+ const PAGE = 10;
2348
+ /**
2349
+ * Atividade da conta (MFA › "Atividade da conta", `SpineFeatures.accountActivity`): as entradas e as tentativas que
2350
+ * falharam desta conta nos últimos 90 dias (`GET {api}/activity` do Argos) — quando, de onde (IP e navegador), em qual
2351
+ * aplicativo, por qual método e, na falha, o motivo. "Só falhas" filtra; "Carregar mais" traz a página seguinte.
2352
+ */
2353
+ class AccountActivityComponent {
2354
+ constructor() {
2355
+ this.api = inject(SpineAccountSecurityService);
2356
+ this.messages = inject(SPINE_MESSAGES);
2357
+ this.t = this.messages.accountFeatures;
2358
+ this.attempts = this.messages.loginAttempts;
2359
+ this.preferences = inject(SpinePreferencesService);
2360
+ this.filter = signal('', ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
2361
+ this.entries = signal([], ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
2362
+ this.total = signal(0, ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
2363
+ this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2364
+ this.loadError = signal('', ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
2365
+ this.filterOptions = computed(() => [
2366
+ { value: '', label: this.t.activityAll },
2367
+ { value: 'failure', label: this.t.activityFailures },
2368
+ ], ...(ngDevMode ? [{ debugName: "filterOptions" }] : /* istanbul ignore next */ []));
2369
+ this.showing = computed(() => formatMessage(this.t.activityShowing, { shown: this.entries().length, total: this.total() }), ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
2370
+ if (this.api.available)
2371
+ void this.load(0);
2372
+ else
2373
+ this.loading.set(false);
2374
+ }
2375
+ setFilter(value) {
2376
+ this.filter.set(value === 'failure' ? 'failure' : '');
2377
+ this.reload();
2378
+ }
2379
+ reload() {
2380
+ this.entries.set([]);
2381
+ void this.load(0);
2382
+ }
2383
+ more() {
2384
+ void this.load(this.entries().length);
2385
+ }
2386
+ async load(offset) {
2387
+ this.loading.set(true);
2388
+ try {
2389
+ const page = await this.api.activity({ limit: PAGE, offset, result: this.filter() || undefined });
2390
+ const attempts = page?.attempts ?? [];
2391
+ this.entries.update((list) => (offset ? [...list, ...attempts] : attempts));
2392
+ this.total.set(page?.total ?? attempts.length);
2393
+ this.loadError.set('');
2394
+ }
2395
+ catch (err) {
2396
+ this.loadError.set(accountErrorCode(err) || 'generic');
2397
+ }
2398
+ finally {
2399
+ this.loading.set(false);
2400
+ }
2401
+ }
2402
+ errorText() {
2403
+ const errors = this.messages.accountSecurity.errors;
2404
+ const code = this.loadError();
2405
+ return code === 'generic' || !errors[code] ? this.t.activityLoadError : errors[code];
2406
+ }
2407
+ resultLabel(a) {
2408
+ return this.attempts.results[a.result] ?? a.result;
2409
+ }
2410
+ resultColor(a) {
2411
+ return a.result === 'success' ? 'success' : a.result === 'failure' ? 'danger' : 'neutral';
2412
+ }
2413
+ method(a) {
2414
+ return this.attempts.methods[a.method] ?? a.method;
2415
+ }
2416
+ reason(a) {
2417
+ return this.attempts.reasons[a.reason ?? ''] ?? a.reason ?? '';
2418
+ }
2419
+ app(a) {
2420
+ return formatMessage(this.t.activityApp, { app: a.client?.name || this.t.unknownApp });
2421
+ }
2422
+ detail(a) {
2423
+ const date = new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeStyle: 'short', timeZone: this.preferences.effectiveTimeZone() })
2424
+ .format(new Date(a.at));
2425
+ return formatMessage(this.t.activityDetail, { date, ip: a.ip || this.t.unknownIp });
2426
+ }
2427
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountActivityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2428
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountActivityComponent, isStandalone: true, selector: "spine-account-activity", ngImport: i0, template: `
2429
+ <div class="sp-account__toolbar">
2430
+ <hm-segmented-control size="small" emphasis="action" [label]="t.activityFilter" [options]="filterOptions()" [value]="filter()" (valueChange)="setFilter($event)" />
2431
+ </div>
2432
+ @if (loading() && !entries().length) {
2433
+ <p class="sp-account__note" role="status">{{ messages.accountSecurity.loading }}</p>
2434
+ } @else if (loadError()) {
2435
+ <hm-alert variant="error">{{ errorText() }}</hm-alert>
2436
+ <div class="sp-account__actions">
2437
+ <hm-button size="small" emphasis="medium" icon="refresh" (clicked)="reload()">{{ messages.accountSecurity.retry }}</hm-button>
2438
+ </div>
2439
+ } @else {
2440
+ <div class="sp-account__list">
2441
+ <ul class="sp-account__items">
2442
+ @for (a of entries(); track a.id) {
2443
+ <li class="sp-account__item">
2444
+ <span class="sp-account__icon" [class.sp-account__icon--accent]="a.result === 'success'"><hm-icon [shape]="a.device.mobile ? 'mobile' : 'computer'" /></span>
2445
+ <span class="sp-account__item-main">
2446
+ <span class="sp-account__item-title">
2447
+ <strong>{{ a.device.label }}</strong>
2448
+ <hm-tag [name]="resultLabel(a)" [color]="resultColor(a)" />
2449
+ </span>
2450
+ <span class="sp-account__meta">{{ detail(a) }}</span>
2451
+ <span class="sp-account__meta">{{ method(a) }}@if (a.reason) { · {{ reason(a) }} }@if (a.client) { · {{ app(a) }} }</span>
2452
+ </span>
2453
+ </li>
2454
+ } @empty {
2455
+ <li class="sp-account__item sp-account__item--empty">{{ t.activityEmpty }}</li>
2456
+ }
2457
+ </ul>
2458
+ @if (entries().length) {
2459
+ <div class="sp-account__list-footer">
2460
+ <span class="sp-account__note">{{ showing() }}</span>
2461
+ @if (entries().length < total()) {
2462
+ <hm-button size="small" emphasis="medium" [loading]="loading()" (clicked)="more()">{{ t.activityMore }}</hm-button>
2463
+ }
2464
+ </div>
2465
+ }
2466
+ </div>
2467
+ }
2468
+ `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2469
+ }
2470
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountActivityComponent, decorators: [{
2471
+ type: Component,
2472
+ args: [{ selector: 'spine-account-activity', imports: [AlertComponent, ButtonComponent, IconComponent, SegmentedControlComponent, TagComponent], template: `
2473
+ <div class="sp-account__toolbar">
2474
+ <hm-segmented-control size="small" emphasis="action" [label]="t.activityFilter" [options]="filterOptions()" [value]="filter()" (valueChange)="setFilter($event)" />
2475
+ </div>
2476
+ @if (loading() && !entries().length) {
2477
+ <p class="sp-account__note" role="status">{{ messages.accountSecurity.loading }}</p>
2478
+ } @else if (loadError()) {
2479
+ <hm-alert variant="error">{{ errorText() }}</hm-alert>
2480
+ <div class="sp-account__actions">
2481
+ <hm-button size="small" emphasis="medium" icon="refresh" (clicked)="reload()">{{ messages.accountSecurity.retry }}</hm-button>
2482
+ </div>
2483
+ } @else {
2484
+ <div class="sp-account__list">
2485
+ <ul class="sp-account__items">
2486
+ @for (a of entries(); track a.id) {
2487
+ <li class="sp-account__item">
2488
+ <span class="sp-account__icon" [class.sp-account__icon--accent]="a.result === 'success'"><hm-icon [shape]="a.device.mobile ? 'mobile' : 'computer'" /></span>
2489
+ <span class="sp-account__item-main">
2490
+ <span class="sp-account__item-title">
2491
+ <strong>{{ a.device.label }}</strong>
2492
+ <hm-tag [name]="resultLabel(a)" [color]="resultColor(a)" />
2493
+ </span>
2494
+ <span class="sp-account__meta">{{ detail(a) }}</span>
2495
+ <span class="sp-account__meta">{{ method(a) }}@if (a.reason) { · {{ reason(a) }} }@if (a.client) { · {{ app(a) }} }</span>
2496
+ </span>
2497
+ </li>
2498
+ } @empty {
2499
+ <li class="sp-account__item sp-account__item--empty">{{ t.activityEmpty }}</li>
2500
+ }
2501
+ </ul>
2502
+ @if (entries().length) {
2503
+ <div class="sp-account__list-footer">
2504
+ <span class="sp-account__note">{{ showing() }}</span>
2505
+ @if (entries().length < total()) {
2506
+ <hm-button size="small" emphasis="medium" [loading]="loading()" (clicked)="more()">{{ t.activityMore }}</hm-button>
2507
+ }
2508
+ </div>
2509
+ }
2510
+ </div>
2511
+ }
2512
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2513
+ }], ctorParameters: () => [] });
2514
+
1600
2515
  /** Métodos que o painel trata (o Argos só tem fluxo para estes; o SMS aparece como indisponível). */
1601
2516
  const SUPPORTED = ['totp', 'email', 'sms'];
1602
2517
  const nameOf = (m) => m?.method_name?.toLowerCase() ?? '';
@@ -1604,7 +2519,7 @@ const isTotp = (m) => nameOf(m) === 'totp';
1604
2519
  const isEmail = (m) => nameOf(m) === 'email';
1605
2520
  const isSms = (m) => nameOf(m) === 'sms';
1606
2521
  /**
1607
- * Estado da seção "Conta e segurança" do painel de configurações: visão geral do Argos e o fluxo de uma
2522
+ * Estado da seção "MFA" do painel de configurações: visão geral do Argos e o fluxo de uma
1608
2523
  * alteração (reautenticação → cadastro/remoção/códigos). Mesmo roteiro da tela "Segurança da conta" do
1609
2524
  * Argos: toda alteração pede antes a prova de identidade recente (senha ou um fator ativo, 5 minutos no
1610
2525
  * servidor); a recusa `reauthentication_required` no meio volta para a prova e a ação segue depois dela.
@@ -1820,7 +2735,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1820
2735
  type: Injectable
1821
2736
  }], ctorParameters: () => [] });
1822
2737
  /**
1823
- * Escopo do estado da conta: no `hm-settings-section` da "Conta e segurança", para as linhas da seção
2738
+ * Escopo do estado da conta: no `hm-settings-section` da seção "MFA", para as linhas da seção
1824
2739
  * (métodos e códigos de recuperação) dividirem o mesmo estado e ele morrer junto com o painel.
1825
2740
  */
1826
2741
  class SpineAccountSecurityScopeDirective {
@@ -1835,7 +2750,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1835
2750
  }]
1836
2751
  }] });
1837
2752
 
1838
- let sequence$3 = 0;
2753
+ let sequence$4 = 0;
1839
2754
  /**
1840
2755
  * Prova de identidade antes de uma alteração na conta (senha, código do autenticador, código enviado ao
1841
2756
  * e-mail cadastrado ou código de recuperação). Vale 5 minutos no Argos, presa à sessão do login; depois
@@ -1845,8 +2760,9 @@ class AccountReauthComponent {
1845
2760
  constructor() {
1846
2761
  this.state = inject(SpineAccountSecurityState);
1847
2762
  this.t = inject(SPINE_MESSAGES).accountSecurity;
1848
- this.titleId = `sp-account-reauth-${++sequence$3}`;
2763
+ this.titleId = `sp-account-reauth-${++sequence$4}`;
1849
2764
  this.codeLabelId = `${this.titleId}-code`;
2765
+ this.hintId = `${this.titleId}-hint`;
1850
2766
  this.password = new FormControl('', { nonNullable: true, validators: [Validators.required] });
1851
2767
  this.recoveryCode = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.minLength(10)] });
1852
2768
  /** Senha, os métodos ativos que servem de prova (o SMS não tem envio) e, com códigos restantes, um código de recuperação. */
@@ -1893,9 +2809,14 @@ class AccountReauthComponent {
1893
2809
  }
1894
2810
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountReauthComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1895
2811
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountReauthComponent, isStandalone: true, selector: "spine-account-reauth", ngImport: i0, template: `
1896
- <section class="sp-account__panel" [attr.aria-labelledby]="titleId">
1897
- <h4 class="sp-account__title" [id]="titleId">{{ t.reauthTitle }}</h4>
1898
- <p class="sp-account__hint">{{ t.reauthHint }}</p>
2812
+ <section class="sp-account__panel" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="hintId">
2813
+ <div class="sp-account__panel-head">
2814
+ <hm-icon shape="shield-check" />
2815
+ <div class="sp-account__panel-text">
2816
+ <h4 class="sp-account__title" [id]="titleId">{{ t.reauthTitle }}</h4>
2817
+ <p class="sp-account__note" [id]="hintId">{{ t.reauthHint }}</p>
2818
+ </div>
2819
+ </div>
1899
2820
 
1900
2821
  @if (choices().length > 1) {
1901
2822
  <hm-radio layout="inline" [label]="t.reauthHow" [options]="choices()" [value]="state.reauthMode()" (valueChange)="choose($event)" />
@@ -1907,7 +2828,7 @@ class AccountReauthComponent {
1907
2828
  <hm-input type="password" autocomplete="current-password" [label]="t.currentPassword" [formControl]="password" [messages]="{ required: t.passwordRequired }" />
1908
2829
  <div class="sp-account__actions">
1909
2830
  <hm-button type="submit" emphasis="high" [loading]="state.busy()">{{ t.confirm }}</hm-button>
1910
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2831
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1911
2832
  </div>
1912
2833
  </form>
1913
2834
  }
@@ -1917,14 +2838,14 @@ class AccountReauthComponent {
1917
2838
  [messages]="{ required: t.recoveryCodeRequired, minlength: t.recoveryCodeLength }" />
1918
2839
  <div class="sp-account__actions">
1919
2840
  <hm-button type="submit" emphasis="high" [loading]="state.busy()">{{ t.confirm }}</hm-button>
1920
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2841
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1921
2842
  </div>
1922
2843
  </form>
1923
2844
  }
1924
2845
  @case ('email-request') {
1925
2846
  <div class="sp-account__actions">
1926
2847
  <hm-button emphasis="high" icon="mail" [loading]="state.busy()" (clicked)="state.sendReauthCode()">{{ t.sendEmailCode }}</hm-button>
1927
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2848
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1928
2849
  </div>
1929
2850
  }
1930
2851
  @default {
@@ -1933,19 +2854,24 @@ class AccountReauthComponent {
1933
2854
  <hm-code-input (completed)="submitCode($event)" />
1934
2855
  </div>
1935
2856
  <div class="sp-account__actions">
1936
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2857
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1937
2858
  </div>
1938
2859
  }
1939
2860
  }
1940
2861
  </section>
1941
- `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CodeInputComponent, selector: "hm-code-input", inputs: ["length"], outputs: ["completed"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: RadioComponent, selector: "hm-radio", inputs: ["value", "label", "options", "appearance", "layout", "hint", "help"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2862
+ `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CodeInputComponent, selector: "hm-code-input", inputs: ["length"], outputs: ["completed"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: RadioComponent, selector: "hm-radio", inputs: ["value", "label", "options", "appearance", "layout", "hint", "help"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1942
2863
  }
1943
2864
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountReauthComponent, decorators: [{
1944
2865
  type: Component,
1945
- args: [{ selector: 'spine-account-reauth', imports: [ReactiveFormsModule, ButtonComponent, CodeInputComponent, InputComponent, RadioComponent], template: `
1946
- <section class="sp-account__panel" [attr.aria-labelledby]="titleId">
1947
- <h4 class="sp-account__title" [id]="titleId">{{ t.reauthTitle }}</h4>
1948
- <p class="sp-account__hint">{{ t.reauthHint }}</p>
2866
+ args: [{ selector: 'spine-account-reauth', imports: [ReactiveFormsModule, ButtonComponent, CodeInputComponent, IconComponent, InputComponent, RadioComponent], template: `
2867
+ <section class="sp-account__panel" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="hintId">
2868
+ <div class="sp-account__panel-head">
2869
+ <hm-icon shape="shield-check" />
2870
+ <div class="sp-account__panel-text">
2871
+ <h4 class="sp-account__title" [id]="titleId">{{ t.reauthTitle }}</h4>
2872
+ <p class="sp-account__note" [id]="hintId">{{ t.reauthHint }}</p>
2873
+ </div>
2874
+ </div>
1949
2875
 
1950
2876
  @if (choices().length > 1) {
1951
2877
  <hm-radio layout="inline" [label]="t.reauthHow" [options]="choices()" [value]="state.reauthMode()" (valueChange)="choose($event)" />
@@ -1957,7 +2883,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1957
2883
  <hm-input type="password" autocomplete="current-password" [label]="t.currentPassword" [formControl]="password" [messages]="{ required: t.passwordRequired }" />
1958
2884
  <div class="sp-account__actions">
1959
2885
  <hm-button type="submit" emphasis="high" [loading]="state.busy()">{{ t.confirm }}</hm-button>
1960
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2886
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1961
2887
  </div>
1962
2888
  </form>
1963
2889
  }
@@ -1967,14 +2893,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1967
2893
  [messages]="{ required: t.recoveryCodeRequired, minlength: t.recoveryCodeLength }" />
1968
2894
  <div class="sp-account__actions">
1969
2895
  <hm-button type="submit" emphasis="high" [loading]="state.busy()">{{ t.confirm }}</hm-button>
1970
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2896
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1971
2897
  </div>
1972
2898
  </form>
1973
2899
  }
1974
2900
  @case ('email-request') {
1975
2901
  <div class="sp-account__actions">
1976
2902
  <hm-button emphasis="high" icon="mail" [loading]="state.busy()" (clicked)="state.sendReauthCode()">{{ t.sendEmailCode }}</hm-button>
1977
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2903
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
1978
2904
  </div>
1979
2905
  }
1980
2906
  @default {
@@ -1983,79 +2909,480 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1983
2909
  <hm-code-input (completed)="submitCode($event)" />
1984
2910
  </div>
1985
2911
  <div class="sp-account__actions">
1986
- <hm-button emphasis="minimal" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2912
+ <hm-button emphasis="medium" [disabled]="state.busy()" (clicked)="state.cancel()">{{ t.cancel }}</hm-button>
2913
+ </div>
2914
+ }
2915
+ }
2916
+ </section>
2917
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2918
+ }] });
2919
+
2920
+ let sequence$3 = 0;
2921
+ /**
2922
+ * Verificação em duas etapas da conta no Argos, dentro do painel de configurações: os métodos (ativo ou não
2923
+ * cadastrado; o SMS, sem fluxo no Argos, só aparece se já estiver ativo) e as ações — adicionar (TOTP com QR Code
2924
+ * e confirmação pelo código; e-mail com código no endereço informado), trocar o aparelho do autenticador e remover. Cada ação passa antes pela
2925
+ * reautenticação (`spine-account-reauth`).
2926
+ */
2927
+ class AccountMfaComponent {
2928
+ constructor() {
2929
+ this.state = inject(SpineAccountSecurityState);
2930
+ this.t = inject(SPINE_MESSAGES).accountSecurity;
2931
+ this.fmt = formatMessage;
2932
+ this.isTotp = isTotp;
2933
+ this.isSms = isSms;
2934
+ this.ids = `sp-account-mfa-${++sequence$3}`;
2935
+ this.email = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] });
2936
+ /** Métodos da lista: o SMS não cadastrado fica de fora (não há como cadastrar daqui). */
2937
+ this.shown = computed(() => this.state.methods().filter((m) => !isSms(m) || m.active), ...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
2938
+ }
2939
+ label(method) {
2940
+ if (!method)
2941
+ return '';
2942
+ if (isTotp(method))
2943
+ return this.t.methodTotp;
2944
+ if (isEmail(method))
2945
+ return this.t.methodEmail;
2946
+ if (isSms(method))
2947
+ return this.t.methodSms;
2948
+ return method.method_name;
2949
+ }
2950
+ icon(method) {
2951
+ return isTotp(method) ? 'authenticator' : isEmail(method) ? 'mail' : 'sms';
2952
+ }
2953
+ status(method) {
2954
+ if (!method.active)
2955
+ return this.t.notRegistered;
2956
+ return method.secret ? formatMessage(this.t.activeWithDetail, { detail: method.secret }) : this.t.active;
2957
+ }
2958
+ /** Método da ação em andamento (para o título "Remover ...?"). */
2959
+ actionMethod() {
2960
+ const action = this.state.action();
2961
+ return action && action.kind !== 'codes' ? action.method : null;
2962
+ }
2963
+ errorText(code) {
2964
+ return this.t.errors[code] ?? this.t.errors.generic;
2965
+ }
2966
+ startAdd(method) {
2967
+ this.email.reset('');
2968
+ this.state.start({ kind: 'add', method });
2969
+ }
2970
+ sendEmail() {
2971
+ this.email.markAsTouched();
2972
+ if (this.email.invalid)
2973
+ return;
2974
+ void this.state.sendEnrollEmail(this.email.value.trim());
2975
+ }
2976
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountMfaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2977
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountMfaComponent, isStandalone: true, selector: "spine-account-mfa", ngImport: i0, template: "@if (state.loading() && !state.overview()) {\n <p class=\"sp-account__note\" role=\"status\">{{ t.loading }}</p>\n} @else if (state.loadError()) {\n <hm-alert variant=\"error\">{{ errorText(state.loadError()) }}</hm-alert>\n @if (state.loadError() !== 'login_required' && state.loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"refresh\" (clicked)=\"state.load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else if (state.overview(); as data) {\n @if (state.owner() === 'methods') {\n @if (state.notice(); as notice) {\n <hm-alert variant=\"success\">{{ t.notices[notice] }}</hm-alert>\n }\n @if (state.error(); as error) {\n <hm-alert variant=\"error\">{{ errorText(error) }}</hm-alert>\n }\n }\n\n @if (state.owner() === 'methods' && state.step() !== 'idle') {\n @switch (state.step()) {\n @case ('reauth') {\n <spine-account-reauth />\n }\n\n @case ('confirm-remove') {\n <section class=\"sp-account__panel sp-account__panel--danger\" [attr.aria-labelledby]=\"ids + '-remove'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"trash\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-remove'\">{{ fmt(t.removeTitle, { method: label(actionMethod()) }) }}</h4>\n <p class=\"sp-account__note\">\n {{ t.removeHint }}\n @if (state.activeMethods().length <= 1) { {{ t.removeLastHint }} }\n </p>\n </div>\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" [loading]=\"state.busy()\" (clicked)=\"state.confirmRemove()\">{{ t.remove }}</hm-button>\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-totp') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-totp'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"authenticator\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-totp'\">{{ t.totpTitle }}</h4>\n @if (state.totpSetup(); as setup) {\n <p class=\"sp-account__note\">{{ t.manualKey }} <code class=\"sp-account__secret\">{{ setup.secret }}</code></p>\n }\n </div>\n </div>\n @if (state.totpSetup()?.qrcode; as qrcode) {\n <div class=\"sp-account__qr\"><img [src]=\"qrcode\" [alt]=\"t.qrAlt\" /></div>\n }\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-totp-code'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-totp-code'\">{{ t.appCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-email') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"mail\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email'\">{{ t.emailTitle }}</h4>\n </div>\n </div>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); sendEmail()\">\n <hm-input type=\"email\" autocomplete=\"email\" [label]=\"t.email\" [placeholder]=\"t.emailPlaceholder\" [formControl]=\"email\"\n [messages]=\"{ required: t.emailRequired, email: t.emailInvalid }\" />\n <div class=\"sp-account__actions\">\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"mail\" [loading]=\"state.busy()\">{{ t.sendCode }}</hm-button>\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n\n @case ('enroll-email-code') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email-code'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"mail\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email-code'\">{{ t.emailCodeTitle }}</h4>\n <p class=\"sp-account__note\" [id]=\"ids + '-email-code-hint'\">{{ fmt(t.emailCodeHint, { email: state.enrollEmail() }) }}</p>\n </div>\n </div>\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-email-code-label'\" [attr.aria-describedby]=\"ids + '-email-code-hint'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-email-code-label'\">{{ t.sixDigitCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n }\n } @else {\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (m of shown(); track m.method_id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"m.active\"><hm-icon [shape]=\"icon(m)\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\"><strong>{{ label(m) }}</strong></span>\n <span class=\"sp-account__meta\">{{ status(m) }}</span>\n </span>\n @if (!isSms(m)) {\n <span class=\"sp-account__item-actions\">\n @if (!m.active) {\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"startAdd(m)\">{{ t.add }}</hm-button>\n } @else {\n @if (isTotp(m)) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"state.start({ kind: 'replace', method: m })\">{{ t.replaceDevice }}</hm-button>\n }\n <hm-button size=\"small\" emphasis=\"medium\" intent=\"danger\" [disabled]=\"state.busy() || state.step() !== 'idle' || !state.canRemove(m)\" (clicked)=\"state.start({ kind: 'remove', method: m })\">{{ t.remove }}</hm-button>\n }\n </span>\n }\n </li>\n }\n </ul>\n @if (data.policy.required) {\n <div class=\"sp-account__list-footer\">\n <span class=\"sp-account__note\">{{ t.requiredHint }}</span>\n </div>\n }\n </div>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CodeInputComponent, selector: "hm-code-input", inputs: ["length"], outputs: ["completed"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: AccountReauthComponent, selector: "spine-account-reauth" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2978
+ }
2979
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountMfaComponent, decorators: [{
2980
+ type: Component,
2981
+ args: [{ selector: 'spine-account-mfa', imports: [ReactiveFormsModule, AlertComponent, ButtonComponent, CodeInputComponent, IconComponent, InputComponent, AccountReauthComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (state.loading() && !state.overview()) {\n <p class=\"sp-account__note\" role=\"status\">{{ t.loading }}</p>\n} @else if (state.loadError()) {\n <hm-alert variant=\"error\">{{ errorText(state.loadError()) }}</hm-alert>\n @if (state.loadError() !== 'login_required' && state.loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"refresh\" (clicked)=\"state.load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else if (state.overview(); as data) {\n @if (state.owner() === 'methods') {\n @if (state.notice(); as notice) {\n <hm-alert variant=\"success\">{{ t.notices[notice] }}</hm-alert>\n }\n @if (state.error(); as error) {\n <hm-alert variant=\"error\">{{ errorText(error) }}</hm-alert>\n }\n }\n\n @if (state.owner() === 'methods' && state.step() !== 'idle') {\n @switch (state.step()) {\n @case ('reauth') {\n <spine-account-reauth />\n }\n\n @case ('confirm-remove') {\n <section class=\"sp-account__panel sp-account__panel--danger\" [attr.aria-labelledby]=\"ids + '-remove'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"trash\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-remove'\">{{ fmt(t.removeTitle, { method: label(actionMethod()) }) }}</h4>\n <p class=\"sp-account__note\">\n {{ t.removeHint }}\n @if (state.activeMethods().length <= 1) { {{ t.removeLastHint }} }\n </p>\n </div>\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" [loading]=\"state.busy()\" (clicked)=\"state.confirmRemove()\">{{ t.remove }}</hm-button>\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-totp') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-totp'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"authenticator\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-totp'\">{{ t.totpTitle }}</h4>\n @if (state.totpSetup(); as setup) {\n <p class=\"sp-account__note\">{{ t.manualKey }} <code class=\"sp-account__secret\">{{ setup.secret }}</code></p>\n }\n </div>\n </div>\n @if (state.totpSetup()?.qrcode; as qrcode) {\n <div class=\"sp-account__qr\"><img [src]=\"qrcode\" [alt]=\"t.qrAlt\" /></div>\n }\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-totp-code'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-totp-code'\">{{ t.appCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-email') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"mail\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email'\">{{ t.emailTitle }}</h4>\n </div>\n </div>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); sendEmail()\">\n <hm-input type=\"email\" autocomplete=\"email\" [label]=\"t.email\" [placeholder]=\"t.emailPlaceholder\" [formControl]=\"email\"\n [messages]=\"{ required: t.emailRequired, email: t.emailInvalid }\" />\n <div class=\"sp-account__actions\">\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"mail\" [loading]=\"state.busy()\">{{ t.sendCode }}</hm-button>\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n\n @case ('enroll-email-code') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email-code'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"mail\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email-code'\">{{ t.emailCodeTitle }}</h4>\n <p class=\"sp-account__note\" [id]=\"ids + '-email-code-hint'\">{{ fmt(t.emailCodeHint, { email: state.enrollEmail() }) }}</p>\n </div>\n </div>\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-email-code-label'\" [attr.aria-describedby]=\"ids + '-email-code-hint'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-email-code-label'\">{{ t.sixDigitCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n }\n } @else {\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (m of shown(); track m.method_id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"m.active\"><hm-icon [shape]=\"icon(m)\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\"><strong>{{ label(m) }}</strong></span>\n <span class=\"sp-account__meta\">{{ status(m) }}</span>\n </span>\n @if (!isSms(m)) {\n <span class=\"sp-account__item-actions\">\n @if (!m.active) {\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"startAdd(m)\">{{ t.add }}</hm-button>\n } @else {\n @if (isTotp(m)) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"state.start({ kind: 'replace', method: m })\">{{ t.replaceDevice }}</hm-button>\n }\n <hm-button size=\"small\" emphasis=\"medium\" intent=\"danger\" [disabled]=\"state.busy() || state.step() !== 'idle' || !state.canRemove(m)\" (clicked)=\"state.start({ kind: 'remove', method: m })\">{{ t.remove }}</hm-button>\n }\n </span>\n }\n </li>\n }\n </ul>\n @if (data.policy.required) {\n <div class=\"sp-account__list-footer\">\n <span class=\"sp-account__note\">{{ t.requiredHint }}</span>\n </div>\n }\n </div>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2982
+ }] });
2983
+
2984
+ /**
2985
+ * Tokens de aplicação pessoais (`x-app-token`) do usuário logado — `argos-admin /me/app-tokens`.
2986
+ * Sempre da aplicação LOGADA: o servidor tira o client do próprio access-token da sessão, então
2987
+ * aqui não existe escolha de aplicação.
2988
+ * Autentica com o access-token da sessão (o servidor recusa um app-token aqui: token não cria
2989
+ * token). `available` é falso quando o app não tem a URL do argos-admin (`SPINE_ADMIN_API`).
2990
+ */
2991
+ class SpineAppTokensService {
2992
+ constructor() {
2993
+ this.http = inject(HttpClient);
2994
+ this.auth = inject(AuthorizationService);
2995
+ this.adminApi = inject(SPINE_ADMIN_API, { optional: true });
2996
+ this.available = !!this.adminApi;
2997
+ /** Sobe a cada token criado por aqui (o "Conectar um agente de IA" cria um; a lista de tokens relê). */
2998
+ this.changes = signal(0, ...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
2999
+ }
3000
+ get baseUrl() {
3001
+ return `${this.adminApi}/me/app-tokens`;
3002
+ }
3003
+ get headers() {
3004
+ return new HttpHeaders({ 'x-access-token': this.auth.getToken() ?? '' });
3005
+ }
3006
+ list() {
3007
+ return firstValueFrom(this.http.get(this.baseUrl, { headers: this.headers }));
3008
+ }
3009
+ options() {
3010
+ return firstValueFrom(this.http.get(`${this.baseUrl}/options`, { headers: this.headers }));
3011
+ }
3012
+ async create(body) {
3013
+ const created = await firstValueFrom(this.http.post(this.baseUrl, body, { headers: this.headers }));
3014
+ this.changes.update((n) => n + 1);
3015
+ return created;
3016
+ }
3017
+ revoke(id) {
3018
+ return firstValueFrom(this.http.delete(`${this.baseUrl}${urlPath `/${id}`}`, { headers: this.headers }));
3019
+ }
3020
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3021
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, providedIn: 'root' }); }
3022
+ }
3023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, decorators: [{
3024
+ type: Injectable,
3025
+ args: [{ providedIn: 'root' }]
3026
+ }] });
3027
+
3028
+ /** Monta o `.mcp.json` do Claude Code: uma entrada por servidor, todas com o mesmo token. Com `write`, cada servidor
3029
+ * que tem `writeScopes` pede esses escopos; os demais (e todos sem `write`) vão com `readScopes`, ou sem o header
3030
+ * (o MCP já é só leitura sem ele). URL relativa é resolvida contra `origin`. */
3031
+ function buildMcpConfig(servers, token, write, origin) {
3032
+ const mcpServers = {};
3033
+ for (const server of servers) {
3034
+ const headers = { 'x-app-token': token };
3035
+ const scopes = write && server.writeScopes ? server.writeScopes : server.readScopes;
3036
+ if (scopes)
3037
+ headers['x-mcp-scopes'] = scopes;
3038
+ mcpServers[server.name] = { type: 'http', url: new URL(server.url, origin).href, headers };
3039
+ }
3040
+ return JSON.stringify({ mcpServers }, null, 2);
3041
+ }
3042
+ /**
3043
+ * Sessões › "Conectar um agente de IA" (`SpineFeatures.aiAgent`): em um passo, cria um token de aplicação pessoal
3044
+ * (`x-app-token`, a mesma API de "Tokens de aplicação") e monta a configuração MCP pronta para o Claude Code, com os
3045
+ * servidores que o app declarou e os escopos do acesso escolhido (só leitura ou leitura e escrita). O token aparece
3046
+ * uma única vez, dentro da configuração; nunca vai para o console nem para o armazenamento do navegador, e some ao
3047
+ * concluir ou fechar o painel.
3048
+ */
3049
+ class AiAgentComponent {
3050
+ constructor() {
3051
+ this.messages = inject(SPINE_MESSAGES);
3052
+ this.t = this.messages.accountFeatures;
3053
+ this.tokens = this.messages.appTokens;
3054
+ this.fmt = formatMessage;
3055
+ this.service = inject(SpineAppTokensService);
3056
+ this.toast = inject(HermesToastService);
3057
+ this.preferences = inject(SpinePreferencesService);
3058
+ this.available = this.service.available;
3059
+ this.servers = inject(SPINE_CONFIG, { optional: true })?.features?.aiAgent?.servers ?? [];
3060
+ this.open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
3061
+ this.starting = signal(false, ...(ngDevMode ? [{ debugName: "starting" }] : /* istanbul ignore next */ []));
3062
+ this.submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3063
+ this.selected = signal(new Set(this.servers.map((s) => s.name)), ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
3064
+ this.access = signal('read', ...(ngDevMode ? [{ debugName: "access" }] : /* istanbul ignore next */ []));
3065
+ this.badges = signal([], ...(ngDevMode ? [{ debugName: "badges" }] : /* istanbul ignore next */ []));
3066
+ this.badgeId = signal('', ...(ngDevMode ? [{ debugName: "badgeId" }] : /* istanbul ignore next */ []));
3067
+ this.validity = signal('90', ...(ngDevMode ? [{ debugName: "validity" }] : /* istanbul ignore next */ []));
3068
+ /** Configuração gerada (com o token), mostrada uma única vez. */
3069
+ this.result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ []));
3070
+ this.canWrite = computed(() => this.servers.some((s) => s.writeScopes && this.selected().has(s.name)), ...(ngDevMode ? [{ debugName: "canWrite" }] : /* istanbul ignore next */ []));
3071
+ this.noBadges = computed(() => !this.badges().length, ...(ngDevMode ? [{ debugName: "noBadges" }] : /* istanbul ignore next */ []));
3072
+ this.canSubmit = computed(() => !!this.selected().size && !!this.badgeId() && !this.submitting(), ...(ngDevMode ? [{ debugName: "canSubmit" }] : /* istanbul ignore next */ []));
3073
+ this.accessOptions = computed(() => [
3074
+ { value: 'read', label: this.t.aiAgentReadOnly },
3075
+ { value: 'write', label: this.t.aiAgentReadWrite },
3076
+ ], ...(ngDevMode ? [{ debugName: "accessOptions" }] : /* istanbul ignore next */ []));
3077
+ this.badgeOptions = computed(() => this.badges().map((b) => ({ value: b.id, label: b.name })), ...(ngDevMode ? [{ debugName: "badgeOptions" }] : /* istanbul ignore next */ []));
3078
+ this.validityOptions = computed(() => [
3079
+ { value: '30', label: this.tokens.days30 },
3080
+ { value: '90', label: this.tokens.days90 },
3081
+ { value: '180', label: this.tokens.months6 },
3082
+ { value: '365', label: this.tokens.year1 },
3083
+ ], ...(ngDevMode ? [{ debugName: "validityOptions" }] : /* istanbul ignore next */ []));
3084
+ }
3085
+ serverLabel(server) {
3086
+ const label = server.label ?? server.name;
3087
+ return server.writeScopes ? label : formatMessage(this.t.aiAgentReadOnlyServer, { name: label });
3088
+ }
3089
+ toggleServer(name, checked) {
3090
+ const next = new Set(this.selected());
3091
+ if (checked)
3092
+ next.add(name);
3093
+ else
3094
+ next.delete(name);
3095
+ this.selected.set(next);
3096
+ }
3097
+ async start() {
3098
+ this.starting.set(true);
3099
+ try {
3100
+ const { badges } = await this.service.options();
3101
+ this.badges.set(badges);
3102
+ this.badgeId.set(badges[0]?.id ?? '');
3103
+ this.access.set('read');
3104
+ this.validity.set('90');
3105
+ this.open.set(true);
3106
+ }
3107
+ catch (err) {
3108
+ this.toast.error(errorDetail(err) || this.tokens.badgesError);
3109
+ }
3110
+ finally {
3111
+ this.starting.set(false);
3112
+ }
3113
+ }
3114
+ cancel() {
3115
+ this.open.set(false);
3116
+ }
3117
+ async generate() {
3118
+ if (!this.canSubmit())
3119
+ return;
3120
+ this.submitting.set(true);
3121
+ try {
3122
+ const date = new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(new Date());
3123
+ const created = await this.service.create({
3124
+ badge_id: this.badgeId(),
3125
+ name: formatMessage(this.t.aiAgentTokenName, { date }).slice(0, 80),
3126
+ validity_days: Number(this.validity()),
3127
+ });
3128
+ const servers = this.servers.filter((s) => this.selected().has(s.name));
3129
+ const write = this.access() === 'write' && this.canWrite();
3130
+ this.result.set({
3131
+ config: buildMcpConfig(servers, created.token, write, location.origin),
3132
+ expires: new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeZone: this.preferences.effectiveTimeZone() })
3133
+ .format(new Date(created.expires_at)),
3134
+ });
3135
+ this.open.set(false);
3136
+ }
3137
+ catch (err) {
3138
+ this.toast.error(errorDetail(err) || this.t.aiAgentCreateError);
3139
+ }
3140
+ finally {
3141
+ this.submitting.set(false);
3142
+ }
3143
+ }
3144
+ async copy() {
3145
+ const config = this.result()?.config;
3146
+ if (!config)
3147
+ return;
3148
+ try {
3149
+ await navigator.clipboard.writeText(config);
3150
+ this.toast.success(this.t.aiAgentCopied);
3151
+ }
3152
+ catch {
3153
+ this.toast.error(this.tokens.copyFailed);
3154
+ }
3155
+ }
3156
+ done() {
3157
+ this.result.set(null);
3158
+ }
3159
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AiAgentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3160
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AiAgentComponent, isStandalone: true, selector: "spine-ai-agent", ngImport: i0, template: `
3161
+ @if (!available) {
3162
+ <p class="sp-account__note">{{ t.aiAgentUnavailable }}</p>
3163
+ } @else if (result(); as r) {
3164
+ <div class="sp-account__panel sp-account__panel--success" role="status">
3165
+ <div class="sp-account__panel-head">
3166
+ <hm-icon shape="check-circle" />
3167
+ <div class="sp-account__panel-text">
3168
+ <p class="sp-account__title">{{ t.aiAgentResult }}</p>
3169
+ <p class="sp-account__note">{{ fmt(t.aiAgentResultHint, { date: r.expires }) }}</p>
3170
+ </div>
3171
+ </div>
3172
+ <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.aiAgentResult" />
3173
+ <div class="sp-account__actions">
3174
+ <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.aiAgentCopy }}</hm-button>
3175
+ <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.aiAgentDone }}</hm-button>
3176
+ </div>
3177
+ </div>
3178
+ } @else if (open()) {
3179
+ <div class="sp-account__panel">
3180
+ <div class="sp-account__form">
3181
+ @if (servers.length > 1) {
3182
+ <fieldset class="sp-account__choices">
3183
+ <legend class="sp-account__label">{{ t.aiAgentServers }}</legend>
3184
+ @for (s of servers; track s.name) {
3185
+ <hm-checkbox [label]="serverLabel(s)" [checked]="selected().has(s.name)" (checkedChange)="toggleServer(s.name, $event)" />
3186
+ }
3187
+ </fieldset>
3188
+ }
3189
+ @if (canWrite()) {
3190
+ <div class="sp-account__field">
3191
+ <span class="sp-account__label">{{ t.aiAgentAccess }}</span>
3192
+ <hm-segmented-control size="small" emphasis="action" [label]="t.aiAgentAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
3193
+ @if (access() === 'write') { <p class="sp-account__note">{{ t.aiAgentWriteHint }}</p> }
3194
+ </div>
3195
+ }
3196
+ @if (badgeOptions().length > 1) {
3197
+ <div class="sp-account__field">
3198
+ <span class="sp-account__label">{{ tokens.accessLevel }}</span>
3199
+ <hm-segmented-control size="small" emphasis="action" [label]="tokens.accessLevel" [options]="badgeOptions()" [value]="badgeId()" (valueChange)="badgeId.set($event)" />
3200
+ </div>
3201
+ }
3202
+ <div class="sp-account__field">
3203
+ <span class="sp-account__label">{{ tokens.validity }}</span>
3204
+ <hm-segmented-control size="small" emphasis="action" [label]="tokens.validity" [options]="validityOptions()" [value]="validity()" (valueChange)="validity.set($event)" />
3205
+ </div>
3206
+ @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.aiAgentSelectServer }}</p> }
3207
+ @if (noBadges()) { <p class="sp-account__note sp-account__note--error">{{ tokens.noBadges }}</p> }
3208
+ </div>
3209
+ <div class="sp-account__actions">
3210
+ <hm-button size="small" emphasis="medium" [disabled]="submitting()" (clicked)="cancel()">{{ tokens.cancel }}</hm-button>
3211
+ <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.aiAgentGenerate }}</hm-button>
3212
+ </div>
3213
+ </div>
3214
+ } @else {
3215
+ <div class="sp-account__actions">
3216
+ <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.aiAgentStart }}</hm-button>
3217
+ </div>
3218
+ }
3219
+ `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: CodeViewerComponent, selector: "hm-code-viewer", inputs: ["code", "language", "filename", "label", "showLineNumbers", "firstLineNumber", "highlightLines", "wrap", "tabSize", "copyable", "header", "virtualize", "virtualizeThreshold", "revealInvisibles"], outputs: ["copied", "tokenized"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3220
+ }
3221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AiAgentComponent, decorators: [{
3222
+ type: Component,
3223
+ args: [{ selector: 'spine-ai-agent', imports: [ButtonComponent, CheckboxComponent, CodeViewerComponent, IconComponent, SegmentedControlComponent], template: `
3224
+ @if (!available) {
3225
+ <p class="sp-account__note">{{ t.aiAgentUnavailable }}</p>
3226
+ } @else if (result(); as r) {
3227
+ <div class="sp-account__panel sp-account__panel--success" role="status">
3228
+ <div class="sp-account__panel-head">
3229
+ <hm-icon shape="check-circle" />
3230
+ <div class="sp-account__panel-text">
3231
+ <p class="sp-account__title">{{ t.aiAgentResult }}</p>
3232
+ <p class="sp-account__note">{{ fmt(t.aiAgentResultHint, { date: r.expires }) }}</p>
3233
+ </div>
3234
+ </div>
3235
+ <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.aiAgentResult" />
3236
+ <div class="sp-account__actions">
3237
+ <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.aiAgentCopy }}</hm-button>
3238
+ <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.aiAgentDone }}</hm-button>
3239
+ </div>
3240
+ </div>
3241
+ } @else if (open()) {
3242
+ <div class="sp-account__panel">
3243
+ <div class="sp-account__form">
3244
+ @if (servers.length > 1) {
3245
+ <fieldset class="sp-account__choices">
3246
+ <legend class="sp-account__label">{{ t.aiAgentServers }}</legend>
3247
+ @for (s of servers; track s.name) {
3248
+ <hm-checkbox [label]="serverLabel(s)" [checked]="selected().has(s.name)" (checkedChange)="toggleServer(s.name, $event)" />
3249
+ }
3250
+ </fieldset>
3251
+ }
3252
+ @if (canWrite()) {
3253
+ <div class="sp-account__field">
3254
+ <span class="sp-account__label">{{ t.aiAgentAccess }}</span>
3255
+ <hm-segmented-control size="small" emphasis="action" [label]="t.aiAgentAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
3256
+ @if (access() === 'write') { <p class="sp-account__note">{{ t.aiAgentWriteHint }}</p> }
3257
+ </div>
3258
+ }
3259
+ @if (badgeOptions().length > 1) {
3260
+ <div class="sp-account__field">
3261
+ <span class="sp-account__label">{{ tokens.accessLevel }}</span>
3262
+ <hm-segmented-control size="small" emphasis="action" [label]="tokens.accessLevel" [options]="badgeOptions()" [value]="badgeId()" (valueChange)="badgeId.set($event)" />
3263
+ </div>
3264
+ }
3265
+ <div class="sp-account__field">
3266
+ <span class="sp-account__label">{{ tokens.validity }}</span>
3267
+ <hm-segmented-control size="small" emphasis="action" [label]="tokens.validity" [options]="validityOptions()" [value]="validity()" (valueChange)="validity.set($event)" />
1987
3268
  </div>
1988
- }
1989
- }
1990
- </section>
1991
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"] }]
3269
+ @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.aiAgentSelectServer }}</p> }
3270
+ @if (noBadges()) { <p class="sp-account__note sp-account__note--error">{{ tokens.noBadges }}</p> }
3271
+ </div>
3272
+ <div class="sp-account__actions">
3273
+ <hm-button size="small" emphasis="medium" [disabled]="submitting()" (clicked)="cancel()">{{ tokens.cancel }}</hm-button>
3274
+ <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.aiAgentGenerate }}</hm-button>
3275
+ </div>
3276
+ </div>
3277
+ } @else {
3278
+ <div class="sp-account__actions">
3279
+ <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.aiAgentStart }}</hm-button>
3280
+ </div>
3281
+ }
3282
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
1992
3283
  }] });
3284
+ function errorDetail(err) {
3285
+ return err instanceof HttpErrorResponse ? (err.error?.detail ?? '') : '';
3286
+ }
1993
3287
 
1994
- let sequence$2 = 0;
1995
3288
  /**
1996
- * Verificação em duas etapas da conta no Argos, dentro do painel de configurações: os métodos (ativo,
1997
- * não cadastrado, indisponível) e as ações — adicionar (TOTP com QR Code e confirmação pelo código; e-mail
1998
- * com código no endereço informado), trocar o aparelho do autenticador e remover. Cada ação passa antes pela
1999
- * reautenticação (`spine-account-reauth`).
3289
+ * Crachá padrão (Conta › "Crachá padrão", `SpineFeatures.defaultBadge`): o nível de acesso com que o login entra
3290
+ * nesta aplicação, guardado no Argos por usuário e aplicação (`GET/PUT {api}/badges`) — vale em qualquer navegador.
3291
+ * "Automático" volta à regra do Argos (o crachá de menor nível). A troca no menu do perfil continua valendo só para a
3292
+ * sessão.
2000
3293
  */
2001
- class AccountMfaComponent {
3294
+ class DefaultBadgeComponent {
2002
3295
  constructor() {
2003
- this.state = inject(SpineAccountSecurityState);
2004
- this.t = inject(SPINE_MESSAGES).accountSecurity;
2005
- this.fmt = formatMessage;
2006
- this.isTotp = isTotp;
2007
- this.isSms = isSms;
2008
- this.ids = `sp-account-mfa-${++sequence$2}`;
2009
- this.email = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] });
2010
- }
2011
- label(method) {
2012
- if (!method)
2013
- return '';
2014
- if (isTotp(method))
2015
- return this.t.methodTotp;
2016
- if (isEmail(method))
2017
- return this.t.methodEmail;
2018
- if (isSms(method))
2019
- return this.t.methodSms;
2020
- return method.method_name;
2021
- }
2022
- icon(method) {
2023
- return isTotp(method) ? 'authenticator' : isEmail(method) ? 'mail' : 'sms';
2024
- }
2025
- status(method) {
2026
- if (isSms(method))
2027
- return this.t.unavailable;
2028
- if (!method.active)
2029
- return this.t.notRegistered;
2030
- return method.secret ? formatMessage(this.t.activeWithDetail, { detail: method.secret }) : this.t.active;
2031
- }
2032
- /** Método da ação em andamento (para o título "Remover ...?"). */
2033
- actionMethod() {
2034
- const action = this.state.action();
2035
- return action && action.kind !== 'codes' ? action.method : null;
2036
- }
2037
- errorText(code) {
2038
- return this.t.errors[code] ?? this.t.errors.generic;
3296
+ this.api = inject(SpineAccountSecurityService);
3297
+ this.toast = inject(HermesToastService);
3298
+ this.t = inject(SPINE_MESSAGES).accountFeatures;
3299
+ this.badges = signal([], ...(ngDevMode ? [{ debugName: "badges" }] : /* istanbul ignore next */ []));
3300
+ this.value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
3301
+ this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
3302
+ this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
3303
+ this.loadError = signal(false, ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
3304
+ this.select = viewChild(SelectComponent, ...(ngDevMode ? [{ debugName: "select" }] : /* istanbul ignore next */ []));
3305
+ void this.load();
2039
3306
  }
2040
- startAdd(method) {
2041
- this.email.reset('');
2042
- this.state.start({ kind: 'add', method });
3307
+ async load() {
3308
+ try {
3309
+ const result = await this.api.badges();
3310
+ this.badges.set(result.badges ?? []);
3311
+ this.value.set(result.default_badge_id ?? '');
3312
+ }
3313
+ catch {
3314
+ this.loadError.set(true);
3315
+ }
3316
+ finally {
3317
+ this.loading.set(false);
3318
+ }
2043
3319
  }
2044
- sendEmail() {
2045
- this.email.markAsTouched();
2046
- if (this.email.invalid)
3320
+ async save(value) {
3321
+ const previous = this.value();
3322
+ if (value === previous)
2047
3323
  return;
2048
- void this.state.sendEnrollEmail(this.email.value.trim());
3324
+ this.value.set(value);
3325
+ this.saving.set(true);
3326
+ try {
3327
+ const result = await this.api.setDefaultBadge(value || null);
3328
+ this.value.set(result.default_badge_id ?? '');
3329
+ this.toast.success(this.t.defaultBadgeSaved);
3330
+ }
3331
+ catch {
3332
+ this.value.set(previous);
3333
+ // O <select> já mostra a escolha recusada: volta ao valor salvo.
3334
+ this.select()?.value.set(previous);
3335
+ this.toast.error(this.t.defaultBadgeError);
3336
+ }
3337
+ finally {
3338
+ this.saving.set(false);
3339
+ }
2049
3340
  }
2050
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountMfaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2051
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountMfaComponent, isStandalone: true, selector: "spine-account-mfa", ngImport: i0, template: "@if (state.loading() && !state.overview()) {\n <p class=\"sp-account__hint\" role=\"status\">{{ t.loading }}</p>\n} @else if (state.loadError()) {\n <hm-alert variant=\"error\">{{ errorText(state.loadError()) }}</hm-alert>\n @if (state.loadError() !== 'login_required' && state.loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" (clicked)=\"state.load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else if (state.overview(); as data) {\n @if (state.owner() === 'methods') {\n @if (state.notice(); as notice) {\n <hm-alert variant=\"success\">{{ t.notices[notice] }}</hm-alert>\n }\n @if (state.error(); as error) {\n <hm-alert variant=\"error\">{{ errorText(error) }}</hm-alert>\n }\n }\n\n @if (state.owner() === 'methods' && state.step() !== 'idle') {\n @switch (state.step()) {\n @case ('reauth') {\n <spine-account-reauth />\n }\n\n @case ('confirm-remove') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-remove'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-remove'\">{{ fmt(t.removeTitle, { method: label(actionMethod()) }) }}</h4>\n <p class=\"sp-account__hint\">\n {{ t.removeHint }}\n @if (state.activeMethods().length <= 1) { {{ t.removeLastHint }} }\n </p>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" [loading]=\"state.busy()\" (clicked)=\"state.confirmRemove()\">{{ t.remove }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-totp') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-totp'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-totp'\">{{ t.totpTitle }}</h4>\n @if (state.totpSetup(); as setup) {\n @if (setup.qrcode) {\n <div class=\"sp-account__qr\"><img [src]=\"setup.qrcode\" [alt]=\"t.qrAlt\" /></div>\n }\n <p class=\"sp-account__hint\">{{ t.manualKey }} <code class=\"sp-account__secret\">{{ setup.secret }}</code></p>\n }\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-totp-code'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-totp-code'\">{{ t.appCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-email') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email'\">{{ t.emailTitle }}</h4>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); sendEmail()\">\n <hm-input type=\"email\" autocomplete=\"email\" [label]=\"t.email\" [placeholder]=\"t.emailPlaceholder\" [formControl]=\"email\"\n [messages]=\"{ required: t.emailRequired, email: t.emailInvalid }\" />\n <div class=\"sp-account__actions\">\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"mail\" [loading]=\"state.busy()\">{{ t.sendCode }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n\n @case ('enroll-email-code') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email-code'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email-code'\">{{ t.emailCodeTitle }}</h4>\n <p class=\"sp-account__hint\" [id]=\"ids + '-email-code-hint'\">{{ fmt(t.emailCodeHint, { email: state.enrollEmail() }) }}</p>\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-email-code-label'\" [attr.aria-describedby]=\"ids + '-email-code-hint'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-email-code-label'\">{{ t.sixDigitCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n }\n } @else {\n @if (data.policy.required) {\n <p class=\"sp-account__hint\">{{ t.requiredHint }}</p>\n }\n <ul class=\"sp-account__list\">\n @for (m of state.methods(); track m.method_id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\"><hm-icon [shape]=\"icon(m)\" /></span>\n <span class=\"sp-account__item-main\">\n <strong>{{ label(m) }}</strong>\n <span class=\"sp-account__meta\">{{ status(m) }}</span>\n </span>\n @if (!isSms(m)) {\n <span class=\"sp-account__item-actions\">\n @if (!m.active) {\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"startAdd(m)\">{{ t.add }}</hm-button>\n } @else {\n @if (isTotp(m)) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"state.start({ kind: 'replace', method: m })\">{{ t.replaceDevice }}</hm-button>\n }\n <hm-button size=\"small\" emphasis=\"minimal\" intent=\"danger\" [disabled]=\"state.busy() || state.step() !== 'idle' || !state.canRemove(m)\" (clicked)=\"state.start({ kind: 'remove', method: m })\">{{ t.remove }}</hm-button>\n }\n </span>\n }\n </li>\n }\n </ul>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CodeInputComponent, selector: "hm-code-input", inputs: ["length"], outputs: ["completed"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: AccountReauthComponent, selector: "spine-account-reauth" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3341
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DefaultBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3342
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DefaultBadgeComponent, isStandalone: true, selector: "spine-default-badge", viewQueries: [{ propertyName: "select", first: true, predicate: SelectComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
3343
+ @if (loadError()) {
3344
+ <hm-alert variant="error">{{ t.defaultBadgeLoadError }}</hm-alert>
3345
+ } @else {
3346
+ <hm-select
3347
+ class="sp-settings__badge"
3348
+ [label]="t.defaultBadge"
3349
+ [labelHidden]="true"
3350
+ [value]="value()"
3351
+ [disabled]="loading() || saving()"
3352
+ (valueChange)="save($event ?? '')"
3353
+ >
3354
+ <option value="">{{ t.defaultBadgeAuto }}</option>
3355
+ @for (b of badges(); track b.id) {
3356
+ <option [value]="b.id">{{ b.name }}</option>
3357
+ }
3358
+ </hm-select>
3359
+ }
3360
+ `, isInline: true, styles: [":host{display:block;min-width:0}.sp-settings__badge{min-inline-size:14rem;max-inline-size:100%}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: SelectComponent, selector: "hm-select", inputs: ["value", "label", "labelHidden", "hint"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2052
3361
  }
2053
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountMfaComponent, decorators: [{
3362
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DefaultBadgeComponent, decorators: [{
2054
3363
  type: Component,
2055
- args: [{ selector: 'spine-account-mfa', imports: [ReactiveFormsModule, AlertComponent, ButtonComponent, CodeInputComponent, IconComponent, InputComponent, AccountReauthComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (state.loading() && !state.overview()) {\n <p class=\"sp-account__hint\" role=\"status\">{{ t.loading }}</p>\n} @else if (state.loadError()) {\n <hm-alert variant=\"error\">{{ errorText(state.loadError()) }}</hm-alert>\n @if (state.loadError() !== 'login_required' && state.loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" (clicked)=\"state.load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else if (state.overview(); as data) {\n @if (state.owner() === 'methods') {\n @if (state.notice(); as notice) {\n <hm-alert variant=\"success\">{{ t.notices[notice] }}</hm-alert>\n }\n @if (state.error(); as error) {\n <hm-alert variant=\"error\">{{ errorText(error) }}</hm-alert>\n }\n }\n\n @if (state.owner() === 'methods' && state.step() !== 'idle') {\n @switch (state.step()) {\n @case ('reauth') {\n <spine-account-reauth />\n }\n\n @case ('confirm-remove') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-remove'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-remove'\">{{ fmt(t.removeTitle, { method: label(actionMethod()) }) }}</h4>\n <p class=\"sp-account__hint\">\n {{ t.removeHint }}\n @if (state.activeMethods().length <= 1) { {{ t.removeLastHint }} }\n </p>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" [loading]=\"state.busy()\" (clicked)=\"state.confirmRemove()\">{{ t.remove }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-totp') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-totp'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-totp'\">{{ t.totpTitle }}</h4>\n @if (state.totpSetup(); as setup) {\n @if (setup.qrcode) {\n <div class=\"sp-account__qr\"><img [src]=\"setup.qrcode\" [alt]=\"t.qrAlt\" /></div>\n }\n <p class=\"sp-account__hint\">{{ t.manualKey }} <code class=\"sp-account__secret\">{{ setup.secret }}</code></p>\n }\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-totp-code'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-totp-code'\">{{ t.appCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n\n @case ('enroll-email') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email'\">{{ t.emailTitle }}</h4>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); sendEmail()\">\n <hm-input type=\"email\" autocomplete=\"email\" [label]=\"t.email\" [placeholder]=\"t.emailPlaceholder\" [formControl]=\"email\"\n [messages]=\"{ required: t.emailRequired, email: t.emailInvalid }\" />\n <div class=\"sp-account__actions\">\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"mail\" [loading]=\"state.busy()\">{{ t.sendCode }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n\n @case ('enroll-email-code') {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-email-code'\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-email-code'\">{{ t.emailCodeTitle }}</h4>\n <p class=\"sp-account__hint\" [id]=\"ids + '-email-code-hint'\">{{ fmt(t.emailCodeHint, { email: state.enrollEmail() }) }}</p>\n <div class=\"sp-account__code\" role=\"group\" [attr.aria-labelledby]=\"ids + '-email-code-label'\" [attr.aria-describedby]=\"ids + '-email-code-hint'\">\n <span class=\"sp-account__label\" [id]=\"ids + '-email-code-label'\">{{ t.sixDigitCode }}</span>\n <hm-code-input (completed)=\"state.confirmEnroll($event)\" />\n </div>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"minimal\" [disabled]=\"state.busy()\" (clicked)=\"state.cancel()\">{{ t.cancel }}</hm-button>\n </div>\n </section>\n }\n }\n } @else {\n @if (data.policy.required) {\n <p class=\"sp-account__hint\">{{ t.requiredHint }}</p>\n }\n <ul class=\"sp-account__list\">\n @for (m of state.methods(); track m.method_id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\"><hm-icon [shape]=\"icon(m)\" /></span>\n <span class=\"sp-account__item-main\">\n <strong>{{ label(m) }}</strong>\n <span class=\"sp-account__meta\">{{ status(m) }}</span>\n </span>\n @if (!isSms(m)) {\n <span class=\"sp-account__item-actions\">\n @if (!m.active) {\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"startAdd(m)\">{{ t.add }}</hm-button>\n } @else {\n @if (isTotp(m)) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"state.busy() || state.step() !== 'idle'\" (clicked)=\"state.start({ kind: 'replace', method: m })\">{{ t.replaceDevice }}</hm-button>\n }\n <hm-button size=\"small\" emphasis=\"minimal\" intent=\"danger\" [disabled]=\"state.busy() || state.step() !== 'idle' || !state.canRemove(m)\" (clicked)=\"state.start({ kind: 'remove', method: m })\">{{ t.remove }}</hm-button>\n }\n </span>\n }\n </li>\n }\n </ul>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"] }]
2056
- }] });
3364
+ args: [{ selector: 'spine-default-badge', imports: [AlertComponent, SelectComponent], template: `
3365
+ @if (loadError()) {
3366
+ <hm-alert variant="error">{{ t.defaultBadgeLoadError }}</hm-alert>
3367
+ } @else {
3368
+ <hm-select
3369
+ class="sp-settings__badge"
3370
+ [label]="t.defaultBadge"
3371
+ [labelHidden]="true"
3372
+ [value]="value()"
3373
+ [disabled]="loading() || saving()"
3374
+ (valueChange)="save($event ?? '')"
3375
+ >
3376
+ <option value="">{{ t.defaultBadgeAuto }}</option>
3377
+ @for (b of badges(); track b.id) {
3378
+ <option [value]="b.id">{{ b.name }}</option>
3379
+ }
3380
+ </hm-select>
3381
+ }
3382
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-width:0}.sp-settings__badge{min-inline-size:14rem;max-inline-size:100%}\n"] }]
3383
+ }], ctorParameters: () => [], propDecorators: { select: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SelectComponent), { isSignal: true }] }] } });
2057
3384
 
2058
- let sequence$1 = 0;
3385
+ let sequence$2 = 0;
2059
3386
  /**
2060
3387
  * Códigos de recuperação da conta no Argos: quantos restam e "gerar novos" (com reautenticação). O lote
2061
3388
  * novo aparece UMA vez (o Argos só guarda o hash), com copiar e baixar em .txt, até a pessoa dizer que
@@ -2067,7 +3394,7 @@ class AccountRecoveryCodesComponent {
2067
3394
  this.messages = inject(SPINE_MESSAGES);
2068
3395
  this.t = this.messages.accountSecurity;
2069
3396
  this.toast = inject(HermesToastService);
2070
- this.titleId = `sp-account-codes-${++sequence$1}`;
3397
+ this.titleId = `sp-account-codes-${++sequence$2}`;
2071
3398
  this.remaining = computed(() => pluralMessage(this.t.codesRemaining, this.state.remainingCodes(), this.messages.meta.locale), ...(ngDevMode ? [{ debugName: "remaining" }] : /* istanbul ignore next */ []));
2072
3399
  }
2073
3400
  errorText(code) {
@@ -2110,35 +3437,52 @@ class AccountRecoveryCodesComponent {
2110
3437
  <spine-account-reauth />
2111
3438
  } @else if (state.owner() === 'codes' && state.step() === 'codes' && state.issuedCodes(); as codes) {
2112
3439
  <section class="sp-account__panel sp-account__panel--success" [attr.aria-labelledby]="titleId">
2113
- <h4 class="sp-account__title" [id]="titleId">{{ t.codesTitle }}</h4>
2114
- <p class="sp-account__hint">{{ t.codesShownOnce }}</p>
3440
+ <div class="sp-account__panel-head">
3441
+ <hm-icon shape="key" />
3442
+ <div class="sp-account__panel-text">
3443
+ <h4 class="sp-account__title" [id]="titleId">{{ t.codesTitle }}</h4>
3444
+ <p class="sp-account__note">{{ t.codesShownOnce }}</p>
3445
+ </div>
3446
+ </div>
2115
3447
  <ol class="sp-account__codes">
2116
3448
  @for (code of codes; track code) {
2117
3449
  <li><code>{{ code }}</code></li>
2118
3450
  }
2119
3451
  </ol>
2120
3452
  <div class="sp-account__actions">
3453
+ <hm-button emphasis="high" (clicked)="state.codesSaved()">{{ t.codesSaved }}</hm-button>
2121
3454
  <hm-button emphasis="medium" icon="copy" (clicked)="copy(codes)">{{ t.copyCodes }}</hm-button>
2122
3455
  <hm-button emphasis="medium" icon="download" (clicked)="download(codes)">{{ t.downloadCodes }}</hm-button>
2123
- <hm-button emphasis="high" (clicked)="state.codesSaved()">{{ t.codesSaved }}</hm-button>
2124
3456
  </div>
2125
3457
  </section>
2126
- } @else if (state.activeMethods().length) {
2127
- <p class="sp-account__hint">{{ remaining() }} {{ t.codesHint }}</p>
2128
- <div class="sp-account__actions">
2129
- <hm-button emphasis="medium" icon="key" [disabled]="state.busy() || state.step() !== 'idle'" (clicked)="state.start({ kind: 'codes' })">
2130
- {{ state.remainingCodes() ? t.regenerateCodes : t.generateCodes }}
2131
- </hm-button>
2132
- </div>
2133
3458
  } @else {
2134
- <p class="sp-account__hint">{{ t.codesNeedMethod }}</p>
3459
+ <ul class="sp-account__list">
3460
+ <li class="sp-account__item">
3461
+ <span class="sp-account__icon" [class.sp-account__icon--accent]="state.remainingCodes() > 0"><hm-icon shape="key" /></span>
3462
+ <span class="sp-account__item-main">
3463
+ @if (state.activeMethods().length) {
3464
+ <span class="sp-account__item-title"><strong>{{ remaining() }}</strong></span>
3465
+ <span class="sp-account__meta">{{ t.codesHint }}</span>
3466
+ } @else {
3467
+ <span class="sp-account__meta">{{ t.codesNeedMethod }}</span>
3468
+ }
3469
+ </span>
3470
+ @if (state.activeMethods().length) {
3471
+ <span class="sp-account__item-actions">
3472
+ <hm-button size="small" emphasis="medium" [disabled]="state.busy() || state.step() !== 'idle'" (clicked)="state.start({ kind: 'codes' })">
3473
+ {{ state.remainingCodes() ? t.regenerateCodes : t.generateCodes }}
3474
+ </hm-button>
3475
+ </span>
3476
+ }
3477
+ </li>
3478
+ </ul>
2135
3479
  }
2136
3480
  }
2137
- `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: AccountReauthComponent, selector: "spine-account-reauth" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3481
+ `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: AccountReauthComponent, selector: "spine-account-reauth" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2138
3482
  }
2139
3483
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountRecoveryCodesComponent, decorators: [{
2140
3484
  type: Component,
2141
- args: [{ selector: 'spine-account-recovery-codes', imports: [AlertComponent, ButtonComponent, AccountReauthComponent], template: `
3485
+ args: [{ selector: 'spine-account-recovery-codes', imports: [AlertComponent, ButtonComponent, IconComponent, AccountReauthComponent], template: `
2142
3486
  @if (state.overview()) {
2143
3487
  @if (state.owner() === 'codes') {
2144
3488
  @if (state.notice(); as notice) {
@@ -2153,31 +3497,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
2153
3497
  <spine-account-reauth />
2154
3498
  } @else if (state.owner() === 'codes' && state.step() === 'codes' && state.issuedCodes(); as codes) {
2155
3499
  <section class="sp-account__panel sp-account__panel--success" [attr.aria-labelledby]="titleId">
2156
- <h4 class="sp-account__title" [id]="titleId">{{ t.codesTitle }}</h4>
2157
- <p class="sp-account__hint">{{ t.codesShownOnce }}</p>
3500
+ <div class="sp-account__panel-head">
3501
+ <hm-icon shape="key" />
3502
+ <div class="sp-account__panel-text">
3503
+ <h4 class="sp-account__title" [id]="titleId">{{ t.codesTitle }}</h4>
3504
+ <p class="sp-account__note">{{ t.codesShownOnce }}</p>
3505
+ </div>
3506
+ </div>
2158
3507
  <ol class="sp-account__codes">
2159
3508
  @for (code of codes; track code) {
2160
3509
  <li><code>{{ code }}</code></li>
2161
3510
  }
2162
3511
  </ol>
2163
3512
  <div class="sp-account__actions">
3513
+ <hm-button emphasis="high" (clicked)="state.codesSaved()">{{ t.codesSaved }}</hm-button>
2164
3514
  <hm-button emphasis="medium" icon="copy" (clicked)="copy(codes)">{{ t.copyCodes }}</hm-button>
2165
3515
  <hm-button emphasis="medium" icon="download" (clicked)="download(codes)">{{ t.downloadCodes }}</hm-button>
2166
- <hm-button emphasis="high" (clicked)="state.codesSaved()">{{ t.codesSaved }}</hm-button>
2167
3516
  </div>
2168
3517
  </section>
2169
- } @else if (state.activeMethods().length) {
2170
- <p class="sp-account__hint">{{ remaining() }} {{ t.codesHint }}</p>
2171
- <div class="sp-account__actions">
2172
- <hm-button emphasis="medium" icon="key" [disabled]="state.busy() || state.step() !== 'idle'" (clicked)="state.start({ kind: 'codes' })">
2173
- {{ state.remainingCodes() ? t.regenerateCodes : t.generateCodes }}
2174
- </hm-button>
2175
- </div>
2176
3518
  } @else {
2177
- <p class="sp-account__hint">{{ t.codesNeedMethod }}</p>
3519
+ <ul class="sp-account__list">
3520
+ <li class="sp-account__item">
3521
+ <span class="sp-account__icon" [class.sp-account__icon--accent]="state.remainingCodes() > 0"><hm-icon shape="key" /></span>
3522
+ <span class="sp-account__item-main">
3523
+ @if (state.activeMethods().length) {
3524
+ <span class="sp-account__item-title"><strong>{{ remaining() }}</strong></span>
3525
+ <span class="sp-account__meta">{{ t.codesHint }}</span>
3526
+ } @else {
3527
+ <span class="sp-account__meta">{{ t.codesNeedMethod }}</span>
3528
+ }
3529
+ </span>
3530
+ @if (state.activeMethods().length) {
3531
+ <span class="sp-account__item-actions">
3532
+ <hm-button size="small" emphasis="medium" [disabled]="state.busy() || state.step() !== 'idle'" (clicked)="state.start({ kind: 'codes' })">
3533
+ {{ state.remainingCodes() ? t.regenerateCodes : t.generateCodes }}
3534
+ </hm-button>
3535
+ </span>
3536
+ }
3537
+ </li>
3538
+ </ul>
2178
3539
  }
2179
3540
  }
2180
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"] }]
3541
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2181
3542
  }] });
2182
3543
 
2183
3544
  /**
@@ -2288,53 +3649,14 @@ class AccountSessionsComponent {
2288
3649
  .format(new Date(value));
2289
3650
  }
2290
3651
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountSessionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2291
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountSessionsComponent, isStandalone: true, selector: "spine-account-sessions", ngImport: i0, template: "@if (loading() && !sessions().length) {\n <p class=\"sp-account__hint\" role=\"status\">{{ t.loading }}</p>\n} @else if (loadError()) {\n <hm-alert variant=\"error\">{{ errorText(loadError()) }}</hm-alert>\n @if (loadError() !== 'login_required' && loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" (clicked)=\"load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else {\n @if (notice()) {\n <hm-alert variant=\"success\">{{ notice() }}</hm-alert>\n }\n @if (error()) {\n <hm-alert variant=\"error\">{{ error() }}</hm-alert>\n }\n\n <ul class=\"sp-account__list\">\n @for (s of ordered(); track s.id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\"><hm-icon [shape]=\"s.device.mobile ? 'mobile' : 'computer'\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\">\n <strong>{{ s.device.label }}</strong>\n @if (s.current) {\n <hm-tag [name]=\"t.thisSession\" color=\"success\" />\n } @else if (s.this_device) {\n <hm-tag [name]=\"t.thisBrowser\" color=\"info\" />\n }\n @if (!s.sso_active) {\n <hm-tag [name]=\"t.appsOnly\" color=\"warning\" />\n }\n </span>\n <span class=\"sp-account__meta\">{{ detail(s) }}</span>\n <span class=\"sp-account__meta\">{{ since(s) }}</span>\n @if (s.clients.length) {\n <span class=\"sp-account__meta\">{{ apps(s) }}</span>\n }\n </span>\n @if (!s.current) {\n <span class=\"sp-account__item-actions\">\n @if (confirming() === s.id) {\n <hm-button size=\"small\" emphasis=\"minimal\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirming() === s.id ? 'high' : 'medium'\" intent=\"danger\" [loading]=\"busy() && confirming() === s.id\" [disabled]=\"busy() && confirming() !== s.id\" (clicked)=\"end(s)\">\n {{ confirming() === s.id ? t.confirmEndSession : t.endSession }}\n </hm-button>\n </span>\n }\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__meta\">{{ t.sessionsEmpty }}</li>\n }\n </ul>\n\n @if (others() > 0) {\n @if (confirming() === 'others') {\n <p class=\"sp-account__hint\">{{ othersConfirm() }}</p>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" icon=\"logout\" [loading]=\"busy()\" (clicked)=\"endOthers()\">{{ t.endOthers }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n </div>\n } @else {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"logout\" [disabled]=\"busy()\" (clicked)=\"ask('others')\">{{ t.endOthers }}</hm-button>\n </div>\n }\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3652
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AccountSessionsComponent, isStandalone: true, selector: "spine-account-sessions", ngImport: i0, template: "@if (loading() && !sessions().length) {\n <p class=\"sp-account__note\" role=\"status\">{{ t.loading }}</p>\n} @else if (loadError()) {\n <hm-alert variant=\"error\">{{ errorText(loadError()) }}</hm-alert>\n @if (loadError() !== 'login_required' && loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"refresh\" (clicked)=\"load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else {\n @if (notice()) {\n <hm-alert variant=\"success\">{{ notice() }}</hm-alert>\n }\n @if (error()) {\n <hm-alert variant=\"error\">{{ error() }}</hm-alert>\n }\n\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (s of ordered(); track s.id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"s.current\"><hm-icon [shape]=\"s.device.mobile ? 'mobile' : 'computer'\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\">\n <strong>{{ s.device.label }}</strong>\n @if (s.current) {\n <hm-tag [name]=\"t.thisSession\" color=\"success\" />\n } @else if (s.this_device) {\n <hm-tag [name]=\"t.thisBrowser\" color=\"info\" />\n }\n @if (!s.sso_active) {\n <hm-tag [name]=\"t.appsOnly\" color=\"warning\" />\n }\n </span>\n <span class=\"sp-account__meta\">{{ detail(s) }}</span>\n <span class=\"sp-account__meta\">{{ since(s) }}@if (s.clients.length) { \u00B7 {{ apps(s) }} }</span>\n </span>\n @if (!s.current) {\n <span class=\"sp-account__item-actions\">\n @if (confirming() === s.id) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirming() === s.id ? 'high' : 'medium'\" intent=\"danger\" [loading]=\"busy() && confirming() === s.id\" [disabled]=\"busy() && confirming() !== s.id\" (clicked)=\"end(s)\">\n {{ confirming() === s.id ? t.confirmEndSession : t.endSession }}\n </hm-button>\n </span>\n }\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__item--empty\">{{ t.sessionsEmpty }}</li>\n }\n </ul>\n\n @if (others() > 0) {\n <div class=\"sp-account__list-footer\">\n @if (confirming() === 'others') {\n <span class=\"sp-account__note\">{{ othersConfirm() }}</span>\n <span class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n <hm-button size=\"small\" emphasis=\"high\" intent=\"danger\" icon=\"logout\" [loading]=\"busy()\" (clicked)=\"endOthers()\">{{ t.endOthers }}</hm-button>\n </span>\n } @else {\n <hm-button size=\"small\" emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [disabled]=\"busy()\" (clicked)=\"ask('others')\">{{ t.endOthers }}</hm-button>\n }\n </div>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2292
3653
  }
2293
3654
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AccountSessionsComponent, decorators: [{
2294
3655
  type: Component,
2295
- args: [{ selector: 'spine-account-sessions', imports: [AlertComponent, ButtonComponent, IconComponent, TagComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (loading() && !sessions().length) {\n <p class=\"sp-account__hint\" role=\"status\">{{ t.loading }}</p>\n} @else if (loadError()) {\n <hm-alert variant=\"error\">{{ errorText(loadError()) }}</hm-alert>\n @if (loadError() !== 'login_required' && loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" (clicked)=\"load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else {\n @if (notice()) {\n <hm-alert variant=\"success\">{{ notice() }}</hm-alert>\n }\n @if (error()) {\n <hm-alert variant=\"error\">{{ error() }}</hm-alert>\n }\n\n <ul class=\"sp-account__list\">\n @for (s of ordered(); track s.id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\"><hm-icon [shape]=\"s.device.mobile ? 'mobile' : 'computer'\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\">\n <strong>{{ s.device.label }}</strong>\n @if (s.current) {\n <hm-tag [name]=\"t.thisSession\" color=\"success\" />\n } @else if (s.this_device) {\n <hm-tag [name]=\"t.thisBrowser\" color=\"info\" />\n }\n @if (!s.sso_active) {\n <hm-tag [name]=\"t.appsOnly\" color=\"warning\" />\n }\n </span>\n <span class=\"sp-account__meta\">{{ detail(s) }}</span>\n <span class=\"sp-account__meta\">{{ since(s) }}</span>\n @if (s.clients.length) {\n <span class=\"sp-account__meta\">{{ apps(s) }}</span>\n }\n </span>\n @if (!s.current) {\n <span class=\"sp-account__item-actions\">\n @if (confirming() === s.id) {\n <hm-button size=\"small\" emphasis=\"minimal\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirming() === s.id ? 'high' : 'medium'\" intent=\"danger\" [loading]=\"busy() && confirming() === s.id\" [disabled]=\"busy() && confirming() !== s.id\" (clicked)=\"end(s)\">\n {{ confirming() === s.id ? t.confirmEndSession : t.endSession }}\n </hm-button>\n </span>\n }\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__meta\">{{ t.sessionsEmpty }}</li>\n }\n </ul>\n\n @if (others() > 0) {\n @if (confirming() === 'others') {\n <p class=\"sp-account__hint\">{{ othersConfirm() }}</p>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" intent=\"danger\" icon=\"logout\" [loading]=\"busy()\" (clicked)=\"endOthers()\">{{ t.endOthers }}</hm-button>\n <hm-button emphasis=\"minimal\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n </div>\n } @else {\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"medium\" icon=\"logout\" [disabled]=\"busy()\" (clicked)=\"ask('others')\">{{ t.endOthers }}</hm-button>\n </div>\n }\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%;min-width:0}.sp-account__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__icon{display:inline-flex;flex:none;color:var(--hm-muted)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 12rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__form,.sp-account__code{display:flex;flex-direction:column;gap:var(--hm-space-xxxs)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:Geist Mono,ui-monospace,monospace;overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}\n"] }]
3656
+ args: [{ selector: 'spine-account-sessions', imports: [AlertComponent, ButtonComponent, IconComponent, TagComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (loading() && !sessions().length) {\n <p class=\"sp-account__note\" role=\"status\">{{ t.loading }}</p>\n} @else if (loadError()) {\n <hm-alert variant=\"error\">{{ errorText(loadError()) }}</hm-alert>\n @if (loadError() !== 'login_required' && loadError() !== 'invalid_token') {\n <div class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"refresh\" (clicked)=\"load()\">{{ t.retry }}</hm-button>\n </div>\n }\n} @else {\n @if (notice()) {\n <hm-alert variant=\"success\">{{ notice() }}</hm-alert>\n }\n @if (error()) {\n <hm-alert variant=\"error\">{{ error() }}</hm-alert>\n }\n\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (s of ordered(); track s.id) {\n <li class=\"sp-account__item\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"s.current\"><hm-icon [shape]=\"s.device.mobile ? 'mobile' : 'computer'\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\">\n <strong>{{ s.device.label }}</strong>\n @if (s.current) {\n <hm-tag [name]=\"t.thisSession\" color=\"success\" />\n } @else if (s.this_device) {\n <hm-tag [name]=\"t.thisBrowser\" color=\"info\" />\n }\n @if (!s.sso_active) {\n <hm-tag [name]=\"t.appsOnly\" color=\"warning\" />\n }\n </span>\n <span class=\"sp-account__meta\">{{ detail(s) }}</span>\n <span class=\"sp-account__meta\">{{ since(s) }}@if (s.clients.length) { \u00B7 {{ apps(s) }} }</span>\n </span>\n @if (!s.current) {\n <span class=\"sp-account__item-actions\">\n @if (confirming() === s.id) {\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirming() === s.id ? 'high' : 'medium'\" intent=\"danger\" [loading]=\"busy() && confirming() === s.id\" [disabled]=\"busy() && confirming() !== s.id\" (clicked)=\"end(s)\">\n {{ confirming() === s.id ? t.confirmEndSession : t.endSession }}\n </hm-button>\n </span>\n }\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__item--empty\">{{ t.sessionsEmpty }}</li>\n }\n </ul>\n\n @if (others() > 0) {\n <div class=\"sp-account__list-footer\">\n @if (confirming() === 'others') {\n <span class=\"sp-account__note\">{{ othersConfirm() }}</span>\n <span class=\"sp-account__actions\">\n <hm-button size=\"small\" emphasis=\"medium\" [disabled]=\"busy()\" (clicked)=\"cancel()\">{{ t.cancel }}</hm-button>\n <hm-button size=\"small\" emphasis=\"high\" intent=\"danger\" icon=\"logout\" [loading]=\"busy()\" (clicked)=\"endOthers()\">{{ t.endOthers }}</hm-button>\n </span>\n } @else {\n <hm-button size=\"small\" emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [disabled]=\"busy()\" (clicked)=\"ask('others')\">{{ t.endOthers }}</hm-button>\n }\n </div>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2296
3657
  }], ctorParameters: () => [] });
2297
3658
 
2298
- /**
2299
- * Tokens de aplicação pessoais (`x-app-token`) do usuário logado — `argos-admin /me/app-tokens`.
2300
- * Sempre da aplicação LOGADA: o servidor tira o client do próprio access-token da sessão, então
2301
- * aqui não existe escolha de aplicação.
2302
- * Autentica com o access-token da sessão (o servidor recusa um app-token aqui: token não cria
2303
- * token). `available` é falso quando o app não tem a URL do argos-admin (`SPINE_ADMIN_API`).
2304
- */
2305
- class SpineAppTokensService {
2306
- constructor() {
2307
- this.http = inject(HttpClient);
2308
- this.auth = inject(AuthorizationService);
2309
- this.adminApi = inject(SPINE_ADMIN_API, { optional: true });
2310
- this.available = !!this.adminApi;
2311
- }
2312
- get baseUrl() {
2313
- return `${this.adminApi}/me/app-tokens`;
2314
- }
2315
- get headers() {
2316
- return new HttpHeaders({ 'x-access-token': this.auth.getToken() ?? '' });
2317
- }
2318
- list() {
2319
- return firstValueFrom(this.http.get(this.baseUrl, { headers: this.headers }));
2320
- }
2321
- options() {
2322
- return firstValueFrom(this.http.get(`${this.baseUrl}/options`, { headers: this.headers }));
2323
- }
2324
- create(body) {
2325
- return firstValueFrom(this.http.post(this.baseUrl, body, { headers: this.headers }));
2326
- }
2327
- revoke(id) {
2328
- return firstValueFrom(this.http.delete(`${this.baseUrl}${urlPath `/${id}`}`, { headers: this.headers }));
2329
- }
2330
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2331
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, providedIn: 'root' }); }
2332
- }
2333
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAppTokensService, decorators: [{
2334
- type: Injectable,
2335
- args: [{ providedIn: 'root' }]
2336
- }] });
2337
-
3659
+ let sequence$1 = 0;
2338
3660
  /** Validades oferecidas (dias). O servidor recusa acima de 365 — o CHECK no banco também. */
2339
3661
  /**
2340
3662
  * Tokens de aplicação do usuário (seção Conta do painel de configurações): lista, criação e
@@ -2352,6 +3674,7 @@ class AppTokensComponent {
2352
3674
  this.toast = inject(HermesToastService);
2353
3675
  this.preferences = inject(SpinePreferencesService);
2354
3676
  this.available = this.service.available;
3677
+ this.ids = `sp-tokens-${++sequence$1}`;
2355
3678
  this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2356
3679
  this.loadError = signal('', ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
2357
3680
  this.tokens = signal([], ...(ngDevMode ? [{ debugName: "tokens" }] : /* istanbul ignore next */ []));
@@ -2383,6 +3706,16 @@ class AppTokensComponent {
2383
3706
  void this.load();
2384
3707
  else
2385
3708
  this.loading.set(false);
3709
+ // Token criado em outro lugar do painel ("Conectar um agente de IA"): relê a lista. O que esta lista cria já relê.
3710
+ let seen = this.service.changes();
3711
+ effect(() => {
3712
+ const changes = this.service.changes();
3713
+ if (changes === seen)
3714
+ return;
3715
+ seen = changes;
3716
+ if (!untracked(this.submitting))
3717
+ void untracked(() => this.load());
3718
+ });
2386
3719
  }
2387
3720
  async load() {
2388
3721
  this.loading.set(true);
@@ -2472,6 +3805,9 @@ class AppTokensComponent {
2472
3805
  this.toast.error(this.errorText(err, this.text.revokeError));
2473
3806
  }
2474
3807
  }
3808
+ cancelRevoke() {
3809
+ this.confirmingRevoke.set(null);
3810
+ }
2475
3811
  formatDate(value) {
2476
3812
  if (!value)
2477
3813
  return this.text.never;
@@ -2483,11 +3819,11 @@ class AppTokensComponent {
2483
3819
  return detail || fallback;
2484
3820
  }
2485
3821
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AppTokensComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2486
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AppTokensComponent, isStandalone: true, selector: "spine-app-tokens", ngImport: i0, template: "@if (!available) {\n <p class=\"sp-tokens__note\">{{ text.unavailable }}</p>\n} @else {\n <!-- Token rec\u00E9m-criado: \u00FAnica vez em que o valor aparece. -->\n @if (created(); as c) {\n <div class=\"sp-tokens__created\" role=\"status\">\n <p class=\"sp-tokens__created-title\">\n <hm-icon shape=\"check-circle\" /> {{ fmt(text.created, { name: c.name }) }}\n </p>\n <code class=\"sp-tokens__secret\">{{ c.token }}</code>\n <p class=\"sp-tokens__note\" [innerHTML]=\"fmtHtml(text.sendHeader, { date: formatDate(c.expiresAt) })\"></p>\n <div class=\"sp-tokens__actions\">\n <hm-button emphasis=\"high\" icon=\"copy\" (clicked)=\"copyCreated()\">{{ text.copyToken }}</hm-button>\n <hm-button emphasis=\"minimal\" (clicked)=\"dismissCreated()\">{{ text.saved }}</hm-button>\n </div>\n </div>\n }\n\n @if (loading()) {\n <p class=\"sp-tokens__note\">{{ text.loading }}</p>\n } @else if (loadError()) {\n <p class=\"sp-tokens__note sp-tokens__note--error\">{{ loadError() }}</p>\n } @else {\n @if (tokens().length) {\n <ul class=\"sp-tokens__list\">\n @for (t of tokens(); track t.id) {\n <li class=\"sp-tokens__item\" [class.sp-tokens__item--expired]=\"t.expired\">\n <span class=\"sp-tokens__item-main\">\n <strong>{{ t.name }}</strong>\n <span class=\"sp-tokens__meta\">{{ t.badge_name }} \u00B7 <code>{{ t.prefix }}</code></span>\n <span class=\"sp-tokens__meta\">\n @if (t.expired) { <span class=\"sp-tokens__expired\">{{ fmt(text.expiredOn, { date: formatDate(t.expires_at) }) }}</span> }\n @else { {{ fmt(text.expiresOn, { date: formatDate(t.expires_at) }) }} }\n \u00B7 {{ fmt(text.lastUsed, { date: formatDate(t.last_used_at) }) }}\n </span>\n </span>\n <hm-button emphasis=\"minimal\" intent=\"danger\" (clicked)=\"revoke(t)\">\n {{ confirmingRevoke() === t.id ? text.confirmRevoke : text.revoke }}\n </hm-button>\n </li>\n }\n </ul>\n } @else if (!creating()) {\n <p class=\"sp-tokens__note\">{{ text.empty }}</p>\n }\n\n @if (creating()) {\n <form class=\"sp-tokens__form\" (submit)=\"$event.preventDefault(); submit()\">\n @if (!badges().length) {\n <p class=\"sp-tokens__note\">{{ text.noBadges }}</p>\n } @else {\n @if (clientName()) {\n <p class=\"sp-tokens__note\" [innerHTML]=\"fmtHtml(text.appOnly, { name: clientName() })\"></p>\n }\n @if (badgeOptions().length > 1) {\n <div class=\"sp-tokens__field\">\n <span class=\"sp-tokens__label\">{{ text.accessLevel }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.accessLevel\" [options]=\"badgeOptions()\" [value]=\"badgeId()\" (valueChange)=\"badgeId.set($event)\" />\n </div>\n }\n <hm-input [label]=\"text.name\" [placeholder]=\"text.namePlaceholder\" [formControl]=\"nameControl\" [messages]=\"{ required: text.nameRequired, maxlength: text.nameMax }\" />\n <div class=\"sp-tokens__field\">\n <span class=\"sp-tokens__label\">{{ text.validity }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.validity\" [options]=\"validityOptions()\" [value]=\"validity()\" (valueChange)=\"validity.set($event)\" />\n </div>\n }\n <div class=\"sp-tokens__actions\">\n @if (badges().length) {\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"key\" [disabled]=\"!canSubmit()\" [loading]=\"submitting()\">{{ text.create }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" (clicked)=\"cancelCreate()\">{{ text.cancel }}</hm-button>\n </div>\n </form>\n } @else {\n <div class=\"sp-tokens__actions\">\n <hm-button emphasis=\"medium\" icon=\"plus\" [disabled]=\"atLimit()\" (clicked)=\"startCreate()\">{{ text.newToken }}</hm-button>\n <span class=\"sp-tokens__note\">{{ fmt(text.validCount, { valid: validCount(), max: maxActive() }) }}</span>\n </div>\n @if (atLimit()) {\n <p class=\"sp-tokens__note\">{{ text.limit }}</p>\n }\n }\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%}.sp-tokens__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-tokens__note--error{color:var(--hm-danger)}.sp-tokens__note code{font-family:Geist Mono,ui-monospace,monospace}.sp-tokens__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-tokens__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-tokens__item+.sp-tokens__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-tokens__item--expired .sp-tokens__item-main strong{color:var(--hm-muted)}.sp-tokens__item-main{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm)}.sp-tokens__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-tokens__meta code{font-family:Geist Mono,ui-monospace,monospace}.sp-tokens__expired{color:var(--hm-danger)}.sp-tokens__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-tokens__form{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-tokens__field{display:flex;flex-direction:column;gap:var(--hm-space-micro)}.sp-tokens__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-tokens__created{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-success);border-radius:var(--hm-radius-control);background:var(--hm-success-bg)}.sp-tokens__created-title{display:flex;align-items:center;gap:var(--hm-space-nano);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-tokens__created-title hm-icon{color:var(--hm-success)}.sp-tokens__secret{display:block;padding:var(--hm-space-nano) var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-family:Geist Mono,ui-monospace,monospace;font-size:var(--hm-font-size-xs);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3822
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AppTokensComponent, isStandalone: true, selector: "spine-app-tokens", ngImport: i0, template: "@if (!available) {\n <p class=\"sp-account__note\">{{ text.unavailable }}</p>\n} @else {\n <!-- Token rec\u00E9m-criado: \u00FAnica vez em que o valor aparece. -->\n @if (created(); as c) {\n <section class=\"sp-account__panel sp-account__panel--success\" role=\"status\" [attr.aria-labelledby]=\"ids + '-created'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"check-circle\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-created'\">{{ fmt(text.created, { name: c.name }) }}</h4>\n <p class=\"sp-account__note\" [innerHTML]=\"fmtHtml(text.sendHeader, { date: formatDate(c.expiresAt) })\"></p>\n </div>\n </div>\n <code class=\"sp-account__secret sp-account__secret--block\">{{ c.token }}</code>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" icon=\"copy\" (clicked)=\"copyCreated()\">{{ text.copyToken }}</hm-button>\n <hm-button emphasis=\"medium\" (clicked)=\"dismissCreated()\">{{ text.saved }}</hm-button>\n </div>\n </section>\n }\n\n @if (loading()) {\n <p class=\"sp-account__note\" role=\"status\">{{ text.loading }}</p>\n } @else if (loadError()) {\n <p class=\"sp-account__note sp-account__note--error\">{{ loadError() }}</p>\n } @else {\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (t of tokens(); track t.id) {\n <li class=\"sp-account__item\" [class.sp-account__item--muted]=\"t.expired\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"!t.expired\"><hm-icon shape=\"key\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\"><strong>{{ t.name }}</strong></span>\n <span class=\"sp-account__meta\">{{ t.badge_name }} \u00B7 <code>{{ t.prefix }}</code></span>\n <span class=\"sp-account__meta\">\n @if (t.expired) { <span class=\"sp-account__meta--danger\">{{ fmt(text.expiredOn, { date: formatDate(t.expires_at) }) }}</span> }\n @else { {{ fmt(text.expiresOn, { date: formatDate(t.expires_at) }) }} }\n \u00B7 {{ fmt(text.lastUsed, { date: formatDate(t.last_used_at) }) }}\n </span>\n </span>\n <span class=\"sp-account__item-actions\">\n @if (confirmingRevoke() === t.id) {\n <hm-button size=\"small\" emphasis=\"medium\" (clicked)=\"cancelRevoke()\">{{ text.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirmingRevoke() === t.id ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"revoke(t)\">\n {{ confirmingRevoke() === t.id ? text.confirmRevoke : text.revoke }}\n </hm-button>\n </span>\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__item--empty\">{{ text.empty }}</li>\n }\n </ul>\n\n @if (!creating()) {\n <div class=\"sp-account__list-footer\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"atLimit()\" (clicked)=\"startCreate()\">{{ text.newToken }}</hm-button>\n <span class=\"sp-account__note\">{{ atLimit() ? text.limit : fmt(text.validCount, { valid: validCount(), max: maxActive() }) }}</span>\n </div>\n }\n </div>\n\n @if (creating()) {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-new'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"key\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-new'\">{{ text.newToken }}</h4>\n @if (clientName()) {\n <p class=\"sp-account__note\" [innerHTML]=\"fmtHtml(text.appOnly, { name: clientName() })\"></p>\n }\n </div>\n </div>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); submit()\">\n @if (!badges().length) {\n <p class=\"sp-account__note\">{{ text.noBadges }}</p>\n } @else {\n @if (badgeOptions().length > 1) {\n <div class=\"sp-account__field\">\n <span class=\"sp-account__label\">{{ text.accessLevel }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.accessLevel\" [options]=\"badgeOptions()\" [value]=\"badgeId()\" (valueChange)=\"badgeId.set($event)\" />\n </div>\n }\n <hm-input [label]=\"text.name\" [placeholder]=\"text.namePlaceholder\" [formControl]=\"nameControl\" [messages]=\"{ required: text.nameRequired, maxlength: text.nameMax }\" />\n <div class=\"sp-account__field\">\n <span class=\"sp-account__label\">{{ text.validity }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.validity\" [options]=\"validityOptions()\" [value]=\"validity()\" (valueChange)=\"validity.set($event)\" />\n </div>\n }\n <div class=\"sp-account__actions\">\n @if (badges().length) {\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"key\" [disabled]=\"!canSubmit()\" [loading]=\"submitting()\">{{ text.create }}</hm-button>\n }\n <hm-button emphasis=\"medium\" (clicked)=\"cancelCreate()\">{{ text.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2487
3823
  }
2488
3824
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AppTokensComponent, decorators: [{
2489
3825
  type: Component,
2490
- args: [{ selector: 'spine-app-tokens', imports: [ReactiveFormsModule, ButtonComponent, InputComponent, IconComponent, SegmentedControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!available) {\n <p class=\"sp-tokens__note\">{{ text.unavailable }}</p>\n} @else {\n <!-- Token rec\u00E9m-criado: \u00FAnica vez em que o valor aparece. -->\n @if (created(); as c) {\n <div class=\"sp-tokens__created\" role=\"status\">\n <p class=\"sp-tokens__created-title\">\n <hm-icon shape=\"check-circle\" /> {{ fmt(text.created, { name: c.name }) }}\n </p>\n <code class=\"sp-tokens__secret\">{{ c.token }}</code>\n <p class=\"sp-tokens__note\" [innerHTML]=\"fmtHtml(text.sendHeader, { date: formatDate(c.expiresAt) })\"></p>\n <div class=\"sp-tokens__actions\">\n <hm-button emphasis=\"high\" icon=\"copy\" (clicked)=\"copyCreated()\">{{ text.copyToken }}</hm-button>\n <hm-button emphasis=\"minimal\" (clicked)=\"dismissCreated()\">{{ text.saved }}</hm-button>\n </div>\n </div>\n }\n\n @if (loading()) {\n <p class=\"sp-tokens__note\">{{ text.loading }}</p>\n } @else if (loadError()) {\n <p class=\"sp-tokens__note sp-tokens__note--error\">{{ loadError() }}</p>\n } @else {\n @if (tokens().length) {\n <ul class=\"sp-tokens__list\">\n @for (t of tokens(); track t.id) {\n <li class=\"sp-tokens__item\" [class.sp-tokens__item--expired]=\"t.expired\">\n <span class=\"sp-tokens__item-main\">\n <strong>{{ t.name }}</strong>\n <span class=\"sp-tokens__meta\">{{ t.badge_name }} \u00B7 <code>{{ t.prefix }}</code></span>\n <span class=\"sp-tokens__meta\">\n @if (t.expired) { <span class=\"sp-tokens__expired\">{{ fmt(text.expiredOn, { date: formatDate(t.expires_at) }) }}</span> }\n @else { {{ fmt(text.expiresOn, { date: formatDate(t.expires_at) }) }} }\n \u00B7 {{ fmt(text.lastUsed, { date: formatDate(t.last_used_at) }) }}\n </span>\n </span>\n <hm-button emphasis=\"minimal\" intent=\"danger\" (clicked)=\"revoke(t)\">\n {{ confirmingRevoke() === t.id ? text.confirmRevoke : text.revoke }}\n </hm-button>\n </li>\n }\n </ul>\n } @else if (!creating()) {\n <p class=\"sp-tokens__note\">{{ text.empty }}</p>\n }\n\n @if (creating()) {\n <form class=\"sp-tokens__form\" (submit)=\"$event.preventDefault(); submit()\">\n @if (!badges().length) {\n <p class=\"sp-tokens__note\">{{ text.noBadges }}</p>\n } @else {\n @if (clientName()) {\n <p class=\"sp-tokens__note\" [innerHTML]=\"fmtHtml(text.appOnly, { name: clientName() })\"></p>\n }\n @if (badgeOptions().length > 1) {\n <div class=\"sp-tokens__field\">\n <span class=\"sp-tokens__label\">{{ text.accessLevel }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.accessLevel\" [options]=\"badgeOptions()\" [value]=\"badgeId()\" (valueChange)=\"badgeId.set($event)\" />\n </div>\n }\n <hm-input [label]=\"text.name\" [placeholder]=\"text.namePlaceholder\" [formControl]=\"nameControl\" [messages]=\"{ required: text.nameRequired, maxlength: text.nameMax }\" />\n <div class=\"sp-tokens__field\">\n <span class=\"sp-tokens__label\">{{ text.validity }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.validity\" [options]=\"validityOptions()\" [value]=\"validity()\" (valueChange)=\"validity.set($event)\" />\n </div>\n }\n <div class=\"sp-tokens__actions\">\n @if (badges().length) {\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"key\" [disabled]=\"!canSubmit()\" [loading]=\"submitting()\">{{ text.create }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" (clicked)=\"cancelCreate()\">{{ text.cancel }}</hm-button>\n </div>\n </form>\n } @else {\n <div class=\"sp-tokens__actions\">\n <hm-button emphasis=\"medium\" icon=\"plus\" [disabled]=\"atLimit()\" (clicked)=\"startCreate()\">{{ text.newToken }}</hm-button>\n <span class=\"sp-tokens__note\">{{ fmt(text.validCount, { valid: validCount(), max: maxActive() }) }}</span>\n </div>\n @if (atLimit()) {\n <p class=\"sp-tokens__note\">{{ text.limit }}</p>\n }\n }\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);width:100%}.sp-tokens__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-tokens__note--error{color:var(--hm-danger)}.sp-tokens__note code{font-family:Geist Mono,ui-monospace,monospace}.sp-tokens__list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control)}.sp-tokens__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) var(--hm-space-xs)}.sp-tokens__item+.sp-tokens__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-tokens__item--expired .sp-tokens__item-main strong{color:var(--hm-muted)}.sp-tokens__item-main{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm)}.sp-tokens__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-tokens__meta code{font-family:Geist Mono,ui-monospace,monospace}.sp-tokens__expired{color:var(--hm-danger)}.sp-tokens__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xxxs)}.sp-tokens__form{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.sp-tokens__field{display:flex;flex-direction:column;gap:var(--hm-space-micro)}.sp-tokens__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-tokens__created{display:flex;flex-direction:column;gap:var(--hm-space-xxxs);padding:var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-success);border-radius:var(--hm-radius-control);background:var(--hm-success-bg)}.sp-tokens__created-title{display:flex;align-items:center;gap:var(--hm-space-nano);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:600}.sp-tokens__created-title hm-icon{color:var(--hm-success)}.sp-tokens__secret{display:block;padding:var(--hm-space-nano) var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-family:Geist Mono,ui-monospace,monospace;font-size:var(--hm-font-size-xs);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}\n"] }]
3826
+ args: [{ selector: 'spine-app-tokens', imports: [ReactiveFormsModule, ButtonComponent, InputComponent, IconComponent, SegmentedControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!available) {\n <p class=\"sp-account__note\">{{ text.unavailable }}</p>\n} @else {\n <!-- Token rec\u00E9m-criado: \u00FAnica vez em que o valor aparece. -->\n @if (created(); as c) {\n <section class=\"sp-account__panel sp-account__panel--success\" role=\"status\" [attr.aria-labelledby]=\"ids + '-created'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"check-circle\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-created'\">{{ fmt(text.created, { name: c.name }) }}</h4>\n <p class=\"sp-account__note\" [innerHTML]=\"fmtHtml(text.sendHeader, { date: formatDate(c.expiresAt) })\"></p>\n </div>\n </div>\n <code class=\"sp-account__secret sp-account__secret--block\">{{ c.token }}</code>\n <div class=\"sp-account__actions\">\n <hm-button emphasis=\"high\" icon=\"copy\" (clicked)=\"copyCreated()\">{{ text.copyToken }}</hm-button>\n <hm-button emphasis=\"medium\" (clicked)=\"dismissCreated()\">{{ text.saved }}</hm-button>\n </div>\n </section>\n }\n\n @if (loading()) {\n <p class=\"sp-account__note\" role=\"status\">{{ text.loading }}</p>\n } @else if (loadError()) {\n <p class=\"sp-account__note sp-account__note--error\">{{ loadError() }}</p>\n } @else {\n <div class=\"sp-account__list\">\n <ul class=\"sp-account__items\">\n @for (t of tokens(); track t.id) {\n <li class=\"sp-account__item\" [class.sp-account__item--muted]=\"t.expired\">\n <span class=\"sp-account__icon\" [class.sp-account__icon--accent]=\"!t.expired\"><hm-icon shape=\"key\" /></span>\n <span class=\"sp-account__item-main\">\n <span class=\"sp-account__item-title\"><strong>{{ t.name }}</strong></span>\n <span class=\"sp-account__meta\">{{ t.badge_name }} \u00B7 <code>{{ t.prefix }}</code></span>\n <span class=\"sp-account__meta\">\n @if (t.expired) { <span class=\"sp-account__meta--danger\">{{ fmt(text.expiredOn, { date: formatDate(t.expires_at) }) }}</span> }\n @else { {{ fmt(text.expiresOn, { date: formatDate(t.expires_at) }) }} }\n \u00B7 {{ fmt(text.lastUsed, { date: formatDate(t.last_used_at) }) }}\n </span>\n </span>\n <span class=\"sp-account__item-actions\">\n @if (confirmingRevoke() === t.id) {\n <hm-button size=\"small\" emphasis=\"medium\" (clicked)=\"cancelRevoke()\">{{ text.cancel }}</hm-button>\n }\n <hm-button size=\"small\" [emphasis]=\"confirmingRevoke() === t.id ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"revoke(t)\">\n {{ confirmingRevoke() === t.id ? text.confirmRevoke : text.revoke }}\n </hm-button>\n </span>\n </li>\n } @empty {\n <li class=\"sp-account__item sp-account__item--empty\">{{ text.empty }}</li>\n }\n </ul>\n\n @if (!creating()) {\n <div class=\"sp-account__list-footer\">\n <hm-button size=\"small\" emphasis=\"medium\" icon=\"plus\" [disabled]=\"atLimit()\" (clicked)=\"startCreate()\">{{ text.newToken }}</hm-button>\n <span class=\"sp-account__note\">{{ atLimit() ? text.limit : fmt(text.validCount, { valid: validCount(), max: maxActive() }) }}</span>\n </div>\n }\n </div>\n\n @if (creating()) {\n <section class=\"sp-account__panel\" [attr.aria-labelledby]=\"ids + '-new'\">\n <div class=\"sp-account__panel-head\">\n <hm-icon shape=\"key\" />\n <div class=\"sp-account__panel-text\">\n <h4 class=\"sp-account__title\" [id]=\"ids + '-new'\">{{ text.newToken }}</h4>\n @if (clientName()) {\n <p class=\"sp-account__note\" [innerHTML]=\"fmtHtml(text.appOnly, { name: clientName() })\"></p>\n }\n </div>\n </div>\n <form class=\"sp-account__form\" (submit)=\"$event.preventDefault(); submit()\">\n @if (!badges().length) {\n <p class=\"sp-account__note\">{{ text.noBadges }}</p>\n } @else {\n @if (badgeOptions().length > 1) {\n <div class=\"sp-account__field\">\n <span class=\"sp-account__label\">{{ text.accessLevel }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.accessLevel\" [options]=\"badgeOptions()\" [value]=\"badgeId()\" (valueChange)=\"badgeId.set($event)\" />\n </div>\n }\n <hm-input [label]=\"text.name\" [placeholder]=\"text.namePlaceholder\" [formControl]=\"nameControl\" [messages]=\"{ required: text.nameRequired, maxlength: text.nameMax }\" />\n <div class=\"sp-account__field\">\n <span class=\"sp-account__label\">{{ text.validity }}</span>\n <hm-segmented-control emphasis=\"action\" [label]=\"text.validity\" [options]=\"validityOptions()\" [value]=\"validity()\" (valueChange)=\"validity.set($event)\" />\n </div>\n }\n <div class=\"sp-account__actions\">\n @if (badges().length) {\n <hm-button type=\"submit\" emphasis=\"high\" icon=\"key\" [disabled]=\"!canSubmit()\" [loading]=\"submitting()\">{{ text.create }}</hm-button>\n }\n <hm-button emphasis=\"medium\" (clicked)=\"cancelCreate()\">{{ text.cancel }}</hm-button>\n </div>\n </form>\n </section>\n }\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
2491
3827
  }], ctorParameters: () => [] });
2492
3828
 
2493
3829
  /** Dependências que valem mostrar em "Sobre" (as demais são detalhe de build). */
@@ -2502,23 +3838,78 @@ const KEY_DEPENDENCIES = [
2502
3838
  { name: 'rxjs', label: 'RxJS' },
2503
3839
  // check-strings: on
2504
3840
  ];
3841
+ /** Lista → chave do `SpineUserStorage` (por usuário). */
3842
+ const BROWSER_LISTS = {
3843
+ recents: SPINE_USER_STORAGE_KEYS.recents,
3844
+ searches: SPINE_USER_STORAGE_KEYS.recentSearches,
3845
+ favorites: SPINE_USER_STORAGE_KEYS.favorites,
3846
+ };
2505
3847
  /**
2506
- * Painel de configurações do shell (substitui as telas do antigo módulo "Sistema"): Aparência,
2507
- * Região, Atalhos, Conta, Conta e segurança (MFA, códigos de recuperação e sessões do Argos) e Sobre, mais
2508
- * as seções que o app registrar com `provideSettingsSection`. Tudo vale na hora — cada controle chama
2509
- * o serviço que já persiste aquela preferência (tema, densidade, menu lateral, fuso).
3848
+ * Painel de configurações do shell (substitui as telas do antigo módulo "Sistema"): Conta (perfil, nível,
3849
+ * sessão, sair), MFA (verificação em duas etapas e códigos de recuperação do Argos), Sessões (token de
3850
+ * acesso, tokens de aplicação e sessões e dispositivos do Argos), Aparência, Acessibilidade (tamanho do texto,
3851
+ * animações), Região (idioma, fuso, formato de hora, primeiro dia da semana), Atalhos, Dados do navegador
3852
+ * (limpar listas, restaurar preferências), Ajuda (novidades, tour) e Sobre, mais as seções que o app registrar
3853
+ * com `provideSettingsSection`. Tudo vale na hora — cada controle chama o serviço que já persiste aquela
3854
+ * preferência (tema, densidade, menu lateral, fuso, tamanho do texto…).
2510
3855
  *
2511
3856
  * Aberto por `SpineSettingsService.open()` — menu do perfil, `Ctrl+,` ou `?settings=<seção>`.
2512
3857
  */
2513
3858
  class SettingsPanelComponent {
3859
+ setThemeMode(mode) {
3860
+ this.theme.setMode(mode);
3861
+ }
3862
+ setFamily(id) {
3863
+ const family = this.families.find((f) => f.id === id);
3864
+ if (family)
3865
+ this.presets.setPreset(family);
3866
+ }
3867
+ setDensity(value) {
3868
+ this.density.setDensity(value);
3869
+ }
3870
+ setFont(value) {
3871
+ this.hermesTheme.setFont(value);
3872
+ }
3873
+ setSidebarMode(value) {
3874
+ this.sidebar.setMode(value);
3875
+ }
3876
+ setTextSize(value) {
3877
+ this.preferences.setTextScale(Number(value));
3878
+ }
3879
+ setMotion(value) {
3880
+ this.preferences.setMotion(value);
3881
+ }
3882
+ setHourCycle(value) {
3883
+ this.preferences.setHourCycle(value);
3884
+ }
3885
+ setWeekStart(value) {
3886
+ this.preferences.setWeekStart(value === '' ? null : Number(value));
3887
+ }
3888
+ /** Fecha o painel antes: novidades e tour ocupam a tela. */
3889
+ showNews() {
3890
+ this.settings.close();
3891
+ this.news.showAll();
3892
+ }
3893
+ replayTour() {
3894
+ this.settings.close();
3895
+ this.onboarding.restart();
3896
+ }
2514
3897
  constructor() {
2515
3898
  this.settings = inject(SpineSettingsService);
2516
3899
  this.messages = inject(SPINE_MESSAGES);
2517
3900
  this.text = this.messages.settings;
2518
3901
  this.fmt = formatMessage;
2519
3902
  this.keys = SPINE_SETTINGS_KEYS;
3903
+ /** Seção aberta, com a chave antiga já trocada pela nova (`seguranca` → `mfa`). */
3904
+ this.activeSection = computed(() => resolveSettingsSection(this.settings.section()), ...(ngDevMode ? [{ debugName: "activeSection" }] : /* istanbul ignore next */ []));
2520
3905
  this.theme = inject(ThemeService);
2521
3906
  this.hermesTheme = inject(HermesThemeService);
3907
+ this.themeConfig = inject(HERMES_THEME_CONFIG);
3908
+ this.sidebarConfig = inject(HERMES_SIDEBAR_CONFIG);
3909
+ this.i18n = inject(HermesI18nService);
3910
+ this.news = inject(SpineNewsService);
3911
+ this.quickAccess = inject(SpineQuickAccessService);
3912
+ this.userStorage = inject(SpineUserStorage);
2522
3913
  this.presets = inject(ThemePresetService);
2523
3914
  this.density = inject(DensityService);
2524
3915
  this.sidebar = inject(HermesSidebarService);
@@ -2532,6 +3923,9 @@ class SettingsPanelComponent {
2532
3923
  this.config = inject(SPINE_CONFIG, { optional: true });
2533
3924
  this.appSections = inject(SPINE_SETTINGS_SECTIONS, { optional: true }) ?? [];
2534
3925
  this.appName = this.config?.appName ?? 'Hermes';
3926
+ /** Recursos opcionais ligados pelo app (`SpineConfig.features`). */
3927
+ this.features = this.config?.features ?? {};
3928
+ this.featureText = this.messages.accountFeatures;
2535
3929
  /**
2536
3930
  * "Copiar token de acesso" é ferramenta de desenvolvimento: põe o token da sessão (Bearer de tudo que a
2537
3931
  * pessoa pode fazer) na área de transferência, ao alcance de qualquer app/extensão que leia o clipboard.
@@ -2561,6 +3955,18 @@ class SettingsPanelComponent {
2561
3955
  this.densityValue = computed(() => this.hermesTheme.density(), ...(ngDevMode ? [{ debugName: "densityValue" }] : /* istanbul ignore next */ []));
2562
3956
  this.fontValue = computed(() => this.hermesTheme.font(), ...(ngDevMode ? [{ debugName: "fontValue" }] : /* istanbul ignore next */ []));
2563
3957
  this.families = this.presets.families;
3958
+ // ── Acessibilidade ──────────────────────────────────────────────────────────
3959
+ /** 100%, 110%, 125%, 150% — no formato de porcentagem do idioma. */
3960
+ this.textSizeOptions = computed(() => {
3961
+ const percent = new Intl.NumberFormat(this.i18n.formatLocale(), { style: 'percent', maximumFractionDigits: 0 });
3962
+ return HERMES_TEXT_SCALES.map((scale) => ({ value: String(scale), label: percent.format(scale) }));
3963
+ }, ...(ngDevMode ? [{ debugName: "textSizeOptions" }] : /* istanbul ignore next */ []));
3964
+ this.textSizeValue = computed(() => String(this.preferences.textScale()), ...(ngDevMode ? [{ debugName: "textSizeValue" }] : /* istanbul ignore next */ []));
3965
+ this.motionOptions = computed(() => [
3966
+ { value: 'system', label: this.text.motionSystem },
3967
+ { value: 'reduce', label: this.text.motionReduce },
3968
+ { value: 'full', label: this.text.motionFull },
3969
+ ], ...(ngDevMode ? [{ debugName: "motionOptions" }] : /* istanbul ignore next */ []));
2564
3970
  // ── Região ──────────────────────────────────────────────────────────────────
2565
3971
  this.languageOptions = computed(() => {
2566
3972
  const appLanguage = this.preferences.languages.find((l) => l.value === this.preferences.appLanguage)?.label ?? this.preferences.appLanguage;
@@ -2582,46 +3988,183 @@ class SettingsPanelComponent {
2582
3988
  this.timeZonePreview = computed(() => {
2583
3989
  const zone = this.preferences.effectiveTimeZone();
2584
3990
  try {
2585
- return new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeStyle: 'short', timeZone: zone }).format(new Date())
3991
+ return new Intl.DateTimeFormat(this.i18n.formatLocale(), { dateStyle: 'short', timeStyle: 'short', timeZone: zone }).format(new Date())
2586
3992
  + ` (${offsetOf(zone)})`;
2587
3993
  }
2588
3994
  catch {
2589
3995
  return zone;
2590
3996
  }
2591
3997
  }, ...(ngDevMode ? [{ debugName: "timeZonePreview" }] : /* istanbul ignore next */ []));
3998
+ this.hourCycleOptions = computed(() => [
3999
+ { value: '', label: this.text.hourCycleAuto },
4000
+ { value: 'h23', label: this.text.hourCycle24 },
4001
+ { value: 'h12', label: this.text.hourCycle12 },
4002
+ ], ...(ngDevMode ? [{ debugName: "hourCycleOptions" }] : /* istanbul ignore next */ []));
4003
+ /** Hora agora, no relógio escolhido — prévia do formato. */
4004
+ this.hourCyclePreview = computed(() => {
4005
+ try {
4006
+ return new Intl.DateTimeFormat(this.i18n.formatLocale(), { timeStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(new Date());
4007
+ }
4008
+ catch {
4009
+ return '';
4010
+ }
4011
+ }, ...(ngDevMode ? [{ debugName: "hourCyclePreview" }] : /* istanbul ignore next */ []));
4012
+ /** Automático, domingo e segunda — os nomes dos dias saem do `Intl`, no idioma. */
4013
+ this.weekStartOptions = computed(() => {
4014
+ const names = weekdayNames(this.i18n.locale(), 'long', 0);
4015
+ return [
4016
+ { value: '', label: this.text.weekStartAuto },
4017
+ { value: '0', label: names[0] },
4018
+ { value: '1', label: names[1] },
4019
+ ];
4020
+ }, ...(ngDevMode ? [{ debugName: "weekStartOptions" }] : /* istanbul ignore next */ []));
4021
+ this.weekStartValue = computed(() => {
4022
+ const day = this.preferences.weekStart();
4023
+ return day === null ? '' : String(day);
4024
+ }, ...(ngDevMode ? [{ debugName: "weekStartValue" }] : /* istanbul ignore next */ []));
2592
4025
  // ── Atalhos ─────────────────────────────────────────────────────────────────
2593
4026
  /** Atalhos registrados (favoritos numa linha só) + os que outra peça dispara — os mesmos do painel "?". */
2594
4027
  this.shortcutItems = this.shortcutService.panelItems;
2595
- this.userStorage = inject(SpineUserStorage);
2596
- // ── Conta e segurança ───────────────────────────────────────────────────────
4028
+ // ── Ajuda ───────────────────────────────────────────────────────────────────
4029
+ /** "Ver novidades" só quando o app tem alguma vigente (`provideSpineNews`). */
4030
+ this.hasNews = this.news.hasAny;
4031
+ // ── Dados do navegador ──────────────────────────────────────────────────────
4032
+ /** Relida quando o painel abre e depois de limpar (as buscas recentes não têm sinal próprio). */
4033
+ this.dataVersion = signal(0, ...(ngDevMode ? [{ debugName: "dataVersion" }] : /* istanbul ignore next */ []));
4034
+ this.counts = computed(() => {
4035
+ this.dataVersion();
4036
+ this.settings.isOpen();
4037
+ return {
4038
+ recents: this.quickAccess.recents().length,
4039
+ searches: this.userStorage.count(BROWSER_LISTS.searches),
4040
+ favorites: this.quickAccess.favorites().length,
4041
+ };
4042
+ }, ...(ngDevMode ? [{ debugName: "counts" }] : /* istanbul ignore next */ []));
4043
+ /** As três listas, com a contagem já no texto da linha. */
4044
+ this.browserLists = computed(() => {
4045
+ const counts = this.counts();
4046
+ const locale = this.i18n.formatLocale();
4047
+ const row = (id, label, keywords, count) => ({ id, label, keywords, count, empty: counts[id] === 0 });
4048
+ return [
4049
+ row('recents', this.text.recentPages, this.text.recentPagesKeywords, pluralMessage(this.text.pagesCount, counts.recents, locale)),
4050
+ row('searches', this.text.recentSearches, this.text.recentSearchesKeywords, pluralMessage(this.text.searchesCount, counts.searches, locale)),
4051
+ row('favorites', this.text.favorites, this.text.favoritesKeywords, pluralMessage(this.text.pagesCount, counts.favorites, locale)),
4052
+ ];
4053
+ }, ...(ngDevMode ? [{ debugName: "browserLists" }] : /* istanbul ignore next */ []));
4054
+ /** Ação destrutiva esperando confirmação (uma por vez, como nas listas do painel). */
4055
+ this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : /* istanbul ignore next */ []));
4056
+ this.logoutService = inject(SpineLogoutService);
4057
+ /** "Saindo…" no botão; volta a `false` também quando a saída falha. */
4058
+ this.logoutPending = this.logoutService.pending;
4059
+ // ── MFA ─────────────────────────────────────────────────────────────────────
2597
4060
  /** API da conta no Argos (Bearer com o token da sessão), a partir do `AUTH_CONFIG`. Sem ela (sem
2598
- * `authority`/`realmId`, ou com `CHANGE_ME`) a seção não aparece. */
4061
+ * `authority`/`realmId`, ou com `CHANGE_ME`) a seção MFA e a linha "Sessões e dispositivos" não aparecem. */
2599
4062
  this.accountSecurity = inject(SpineAccountSecurityService).available;
4063
+ // ── Sessões ─────────────────────────────────────────────────────────────────
4064
+ /** Linhas da seção Sessões, cada uma com a própria condição. "Tokens de aplicação" sempre aparece (sem
4065
+ * `SPINE_ADMIN_API` os tokens explicam que não estão disponíveis); o token de acesso só com
4066
+ * `technicalDocs`; sessões e dispositivos só com a API da conta. A "Sessão" (validade) fica em Conta. */
4067
+ this.sessionsItems = {
4068
+ accessToken: this.showAccessToken,
4069
+ appTokens: true,
4070
+ aiAgent: !!this.config?.features?.aiAgent?.servers?.length,
4071
+ devices: this.accountSecurity,
4072
+ };
4073
+ /** Sem nenhuma linha visível a seção não aparece. */
4074
+ this.hasSessionsSection = Object.values(this.sessionsItems).some(Boolean);
4075
+ // ── Recursos opcionais (SpineConfig.features) ──────────────────────────────
4076
+ this.idle = inject(SpineIdleService);
4077
+ this.startPageService = inject(SpineStartPageService);
4078
+ this.router = inject(Router);
4079
+ this.notices = inject(SpineNoticesService);
4080
+ this.accountPreferences = inject(SpineAccountPreferencesService);
4081
+ /** Conta › "Encerrar por inatividade": opções dentro dos limites do app. */
4082
+ this.idleOptions = computed(() => this.idle.limits.options.map((m) => ({ value: String(m), label: m ? formatMessage(this.featureText.idleOption, { minutes: m }) : this.featureText.idleNever })), ...(ngDevMode ? [{ debugName: "idleOptions" }] : /* istanbul ignore next */ []));
4083
+ this.idleValue = computed(() => String(this.idle.minutes()), ...(ngDevMode ? [{ debugName: "idleValue" }] : /* istanbul ignore next */ []));
4084
+ this.idleHint = this.idle.limits.max ? formatMessage(this.featureText.idleLimitHint, { minutes: this.idle.limits.max }) : '';
4085
+ /** Conta › "Tela inicial": início, última tela e cada favorito. */
4086
+ this.startPageOptions = computed(() => [
4087
+ { value: 'home', label: this.featureText.startHome },
4088
+ { value: 'last', label: this.featureText.startLast },
4089
+ ...this.quickAccess.favorites().map((f) => ({ value: `fav:${f.id}`, label: formatMessage(this.featureText.startFavorite, { name: f.displayName || f.redirect }) })),
4090
+ ], ...(ngDevMode ? [{ debugName: "startPageOptions" }] : /* istanbul ignore next */ []));
4091
+ /** Favorito escolhido que deixou de ser favorito: mostra "Início" (é para onde o login vai). */
4092
+ this.startPageValue = computed(() => {
4093
+ const value = this.preferences.startPage();
4094
+ return this.startPageOptions().some((o) => o.value === value) ? value : 'home';
4095
+ }, ...(ngDevMode ? [{ debugName: "startPageValue" }] : /* istanbul ignore next */ []));
4096
+ this.quickAccessHasFavorites = computed(() => this.quickAccess.favorites().length > 0, ...(ngDevMode ? [{ debugName: "quickAccessHasFavorites" }] : /* istanbul ignore next */ []));
4097
+ /** "Preferências na conta": situação da sincronização. */
4098
+ this.syncStatus = computed(() => {
4099
+ const status = this.accountPreferences.status();
4100
+ const at = this.accountPreferences.syncedAt();
4101
+ if (status === 'local')
4102
+ return this.featureText.syncLocal;
4103
+ if (status === 'error')
4104
+ return this.featureText.syncError;
4105
+ if (status === 'saving' || status === 'loading')
4106
+ return this.featureText.syncSaving;
4107
+ if (!at)
4108
+ return '';
4109
+ const time = new Intl.DateTimeFormat(this.i18n.formatLocale(), { timeStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(at);
4110
+ return formatMessage(this.featureText.syncSynced, { time });
4111
+ }, ...(ngDevMode ? [{ debugName: "syncStatus" }] : /* istanbul ignore next */ []));
2600
4112
  // ── Sobre ───────────────────────────────────────────────────────────────────
2601
4113
  this.dependencies = KEY_DEPENDENCIES
2602
4114
  .map((d) => ({ ...d, version: this.packageInfo.dependencies?.[d.name] }))
2603
4115
  .filter((d) => !!d.version);
4116
+ // Painel fechado por qualquer caminho: a confirmação pendente não reaparece na próxima abertura.
4117
+ effect(() => {
4118
+ if (!this.settings.isOpen())
4119
+ this.confirming.set(null);
4120
+ });
2604
4121
  }
2605
- setThemeMode(mode) {
2606
- this.theme.setMode(mode);
2607
- }
2608
- setFamily(id) {
2609
- const family = this.families.find((f) => f.id === id);
2610
- if (family)
2611
- this.presets.setPreset(family);
2612
- }
2613
- setDensity(value) {
2614
- this.density.setDensity(value);
2615
- }
2616
- setFont(value) {
2617
- this.hermesTheme.setFont(value);
4122
+ cancelConfirm() {
4123
+ this.confirming.set(null);
2618
4124
  }
2619
- setSidebarMode(value) {
2620
- this.sidebar.setMode(value);
4125
+ /** 1º clique pede confirmação; o 2º apaga a lista do usuário atual e avisa. */
4126
+ clearList(list) {
4127
+ if (this.confirming() !== list) {
4128
+ this.confirming.set(list);
4129
+ return;
4130
+ }
4131
+ this.confirming.set(null);
4132
+ this.userStorage.clear(BROWSER_LISTS[list]);
4133
+ this.dataVersion.update((v) => v + 1);
4134
+ const done = { recents: this.text.recentPagesCleared, searches: this.text.recentSearchesCleared, favorites: this.text.favoritesCleared };
4135
+ this.toast.success(done[list]);
2621
4136
  }
2622
- replayTour() {
2623
- this.settings.close();
2624
- this.onboarding.restart();
4137
+ /**
4138
+ * Restaurar preferências (com confirmação): tema (sistema), cor principal (a padrão do app), densidade
4139
+ * (confortável), fonte (a padrão do app), menu lateral (o modo padrão do app), tamanho do texto (100%),
4140
+ * animações (sistema), formato de hora e primeiro dia da semana (automáticos). Idioma, fuso horário e
4141
+ * atalhos de tecla única ficam: mudam como a pessoa lê e digita, e não são "aparência".
4142
+ */
4143
+ restorePreferences() {
4144
+ if (this.confirming() !== 'preferences') {
4145
+ this.confirming.set('preferences');
4146
+ return;
4147
+ }
4148
+ this.confirming.set(null);
4149
+ const config = this.themeConfig;
4150
+ this.theme.setMode('system');
4151
+ const families = this.families;
4152
+ const knownFamily = (id) => !!id && families.some((f) => f.id === (config.aliases?.[id] ?? id));
4153
+ const family = [config.defaultFamily, FALLBACK_FAMILY, families[0]?.id].find(knownFamily);
4154
+ if (family)
4155
+ this.hermesTheme.setFamily(family);
4156
+ this.density.setDensity('comfortable');
4157
+ const fonts = this.hermesTheme.fonts ?? HERMES_FONTS;
4158
+ const knownFont = (id) => !!id && fonts.some((f) => f.id === (config.fontAliases?.[id] ?? id));
4159
+ const font = [config.defaultFont, 'geist', fonts[0]?.id].find(knownFont);
4160
+ if (font)
4161
+ this.hermesTheme.setFont(font);
4162
+ this.sidebar.setMode(this.sidebarConfig.defaultMode ?? 'auto');
4163
+ this.preferences.setTextScale(1);
4164
+ this.preferences.setMotion('system');
4165
+ this.preferences.setHourCycle('');
4166
+ this.preferences.setWeekStart(null);
4167
+ this.toast.success(this.text.preferencesRestored);
2625
4168
  }
2626
4169
  // ── Conta ───────────────────────────────────────────────────────────────────
2627
4170
  get profile() { return this.auth.profile; }
@@ -2633,7 +4176,7 @@ class SettingsPanelComponent {
2633
4176
  return this.text.sessionUnknown;
2634
4177
  const at = new Date(exp * 1000);
2635
4178
  const minutes = Math.round((at.getTime() - Date.now()) / 60000);
2636
- const when = new Intl.DateTimeFormat(this.messages.meta.locale, { timeStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(at);
4179
+ const when = new Intl.DateTimeFormat(this.i18n.formatLocale(), { timeStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(at);
2637
4180
  if (minutes <= 0)
2638
4181
  return formatMessage(this.text.sessionExpired, { time: when });
2639
4182
  return minutes < 60
@@ -2650,10 +4193,10 @@ class SettingsPanelComponent {
2650
4193
  }
2651
4194
  await this.copy(token, this.text.tokenCopied);
2652
4195
  }
2653
- logout() {
2654
- this.settings.close();
2655
- this.userStorage.clearHistory();
2656
- this.auth.logout();
4196
+ /** O painel fica aberto até a saída dar certo: com falha, a pessoa continua aqui, com o aviso de erro. */
4197
+ async logout() {
4198
+ if (await this.logoutService.logout())
4199
+ this.settings.close();
2657
4200
  }
2658
4201
  /** Login da pessoa (`preferred_username` do token, ou o e-mail), para dizer QUAL conta do Argos. */
2659
4202
  get accountLogin() {
@@ -2661,12 +4204,40 @@ class SettingsPanelComponent {
2661
4204
  const login = payload?.['preferred_username'] ?? payload?.['email'];
2662
4205
  return typeof login === 'string' ? login : '';
2663
4206
  }
4207
+ /** Realm pelo nome (`master`, `Minha Empresa`); um id (UUID) não diz nada a quem lê e fica de fora. */
2664
4208
  get accountRealm() {
2665
- return this.authConfig?.realmId ?? '';
4209
+ const realm = this.authConfig?.realmId?.trim() ?? '';
4210
+ return UUID.test(realm) ? '' : realm;
4211
+ }
4212
+ setIdle(value) {
4213
+ this.preferences.setIdleMinutes(Number(value));
4214
+ }
4215
+ setStartPage(value) {
4216
+ this.preferences.setStartPage(value);
4217
+ // "Última tela visitada" começa a valer já com a tela de agora.
4218
+ if (value === 'last')
4219
+ this.startPageService.remember(this.router.url);
4220
+ }
4221
+ /** Conta › "Crachá padrão": só com mais de um crachá nesta aplicação (claim `badges` do token). */
4222
+ get showDefaultBadge() {
4223
+ return !!this.features.defaultBadge && this.accountSecurity && (this.auth.badges?.length ?? 0) > 1;
4224
+ }
4225
+ /** Notificações: aviso do navegador ligado (escolha + permissão). */
4226
+ async setBrowserNotices(enabled) {
4227
+ if (!enabled) {
4228
+ this.notices.disableBrowser();
4229
+ return;
4230
+ }
4231
+ const ok = await this.notices.enableBrowser();
4232
+ if (!ok)
4233
+ this.toast.warning(this.notices.permission() === 'unsupported' ? this.featureText.browserNoticesUnsupported : this.featureText.browserNoticesDenied);
4234
+ }
4235
+ syncNow() {
4236
+ void this.accountPreferences.syncNow();
2666
4237
  }
2667
4238
  get buildDate() {
2668
4239
  const date = this.packageInfo.buildDate;
2669
- return date ? new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeStyle: 'short' }).format(new Date(date)) : '';
4240
+ return date ? new Intl.DateTimeFormat(this.i18n.formatLocale(), { dateStyle: 'short', timeStyle: 'short' }).format(new Date(date)) : '';
2670
4241
  }
2671
4242
  /** Texto pronto pra colar num chamado: app, versão, ambiente, navegador, tela, fuso e página. */
2672
4243
  async copySupportInfo() {
@@ -2704,18 +4275,19 @@ class SettingsPanelComponent {
2704
4275
  this.settings.close();
2705
4276
  }
2706
4277
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SettingsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2707
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SettingsPanelComponent, isStandalone: true, selector: "spine-settings-panel", ngImport: i0, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"settings.section()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (showAccessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <spine-app-tokens />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Conta e seguran\u00E7a \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA, c\u00F3digos de recupera\u00E7\u00E3o e sess\u00F5es aqui\n mesmo, sem abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o,\n cadastro em andamento), que morre quando o painel fecha. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.security\" [label]=\"text.security\" icon=\"shield-check\" [description]=\"text.securityDescription\" [keywords]=\"text.securityKeywords\">\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\">\n @if (accountRealm) { <span rowDescription class=\"sp-settings__hint\">{{ fmt(text.signedInRealm, { realm: accountRealm }) }}</span> }\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [keywords]=\"text.timeZoneKeywords\">\n <span rowDescription class=\"sp-settings__hint\">{{ fmt(text.timeZoneHint, { now: timeZonePreview() }) }}</span>\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n <hm-settings-row [label]=\"text.tour\" [description]=\"text.tourDescription\" [keywords]=\"text.tourKeywords\">\n <hm-button emphasis=\"medium\" icon=\"play\" (clicked)=\"replayTour()\">{{ text.replayTour }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"minimal\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"minimal\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"minimal\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__hint{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text)}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:Geist Mono,ui-monospace,monospace}.sp-settings__links{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-nano)}.sp-settings__deps{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xxxs);padding:var(--hm-space-nano) var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-hover)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: AppTokensComponent, selector: "spine-app-tokens" }, { kind: "component", type: AccountMfaComponent, selector: "spine-account-mfa" }, { kind: "component", type: AccountRecoveryCodesComponent, selector: "spine-account-recovery-codes" }, { kind: "component", type: AccountSessionsComponent, selector: "spine-account-sessions" }, { kind: "directive", type: SpineAccountSecurityScopeDirective, selector: "[spineAccountSecurity]" }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "help", "size", "labelHidden", "clearable", "requiredManual", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SettingsDialogComponent, selector: "hm-settings-dialog", inputs: ["open", "heading", "activeSection", "closeHandler", "actions", "submitLabel", "cancelLabel"], outputs: ["openChange", "activeSectionChange", "closed", "submitted"] }, { kind: "component", type: SettingsRowComponent, selector: "hm-settings-row", inputs: ["label", "description", "keywords", "stacked"] }, { kind: "component", type: SettingsSectionComponent, selector: "hm-settings-section", inputs: ["key", "label", "icon", "description", "keywords", "control", "invalid"] }, { kind: "component", type: SelectComponent, selector: "hm-select", inputs: ["value", "label", "labelHidden", "hint"], outputs: ["valueChange"] }, { kind: "component", type: ShortcutListComponent, selector: "hm-shortcut-list", inputs: ["items", "registered", "searchable", "characterKeysToggle", "headingLevel"] }, { kind: "component", type: ThemePickerComponent, selector: "hm-theme-picker", inputs: ["families", "family", "theme", "showMode"], outputs: ["familyChosen", "themeChosen"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4278
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SettingsPanelComponent, isStandalone: true, selector: "spine-settings-panel", ngImport: i0, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.aiAgent). -->\n @if (sessionsItems.aiAgent) {\n <hm-settings-row [label]=\"featureText.aiAgent\" [description]=\"featureText.aiAgentDescription\" [keywords]=\"featureText.aiAgentKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.aiAgent })\" [text]=\"featureText.aiAgentHelp\" />\n <spine-ai-agent />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente) e tour: os dois fecham o painel, que ocupa a tela. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.tour\" [description]=\"text.tourDescription\" [keywords]=\"text.tourKeywords\">\n <hm-button emphasis=\"medium\" icon=\"play\" (clicked)=\"replayTour()\">{{ text.replayTour }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: AppTokensComponent, selector: "spine-app-tokens" }, { kind: "component", type: AiAgentComponent, selector: "spine-ai-agent" }, { kind: "component", type: AccountActivityComponent, selector: "spine-account-activity" }, { kind: "component", type: DefaultBadgeComponent, selector: "spine-default-badge" }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: AccountMfaComponent, selector: "spine-account-mfa" }, { kind: "component", type: AccountRecoveryCodesComponent, selector: "spine-account-recovery-codes" }, { kind: "component", type: AccountSessionsComponent, selector: "spine-account-sessions" }, { kind: "directive", type: SpineAccountSecurityScopeDirective, selector: "[spineAccountSecurity]" }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "help", "size", "labelHidden", "clearable", "requiredManual", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SettingsDialogComponent, selector: "hm-settings-dialog", inputs: ["open", "heading", "activeSection", "closeHandler", "actions", "submitLabel", "cancelLabel"], outputs: ["openChange", "activeSectionChange", "closed", "submitted"] }, { kind: "component", type: SettingsRowComponent, selector: "hm-settings-row", inputs: ["label", "description", "keywords", "stacked"] }, { kind: "component", type: SettingsSectionComponent, selector: "hm-settings-section", inputs: ["key", "label", "icon", "description", "keywords", "control", "invalid"] }, { kind: "component", type: SelectComponent, selector: "hm-select", inputs: ["value", "label", "labelHidden", "hint"], outputs: ["valueChange"] }, { kind: "component", type: ShortcutListComponent, selector: "hm-shortcut-list", inputs: ["items", "registered", "searchable", "characterKeysToggle", "headingLevel"] }, { kind: "component", type: ThemePickerComponent, selector: "hm-theme-picker", inputs: ["families", "family", "theme", "showMode"], outputs: ["familyChosen", "themeChosen"] }, { kind: "component", type: ToggletipComponent, selector: "hm-toggletip", inputs: ["label", "text", "side", "intent", "icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2708
4279
  }
2709
4280
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SettingsPanelComponent, decorators: [{
2710
4281
  type: Component,
2711
4282
  args: [{ selector: 'spine-settings-panel', imports: [
2712
- NgComponentOutlet, AppTokensComponent,
4283
+ NgComponentOutlet, AppTokensComponent, AiAgentComponent, AccountActivityComponent, DefaultBadgeComponent, CheckboxComponent,
2713
4284
  AccountMfaComponent, AccountRecoveryCodesComponent, AccountSessionsComponent, SpineAccountSecurityScopeDirective,
2714
- AccountAvatarComponent, ButtonComponent, ComboboxComponent, IconComponent,
4285
+ AccountAvatarComponent, ButtonComponent, ComboboxComponent,
2715
4286
  SegmentedControlComponent, SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent,
2716
- SelectComponent, ShortcutListComponent, ThemePickerComponent,
2717
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"settings.section()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (showAccessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <spine-app-tokens />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Conta e seguran\u00E7a \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA, c\u00F3digos de recupera\u00E7\u00E3o e sess\u00F5es aqui\n mesmo, sem abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o,\n cadastro em andamento), que morre quando o painel fecha. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.security\" [label]=\"text.security\" icon=\"shield-check\" [description]=\"text.securityDescription\" [keywords]=\"text.securityKeywords\">\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\">\n @if (accountRealm) { <span rowDescription class=\"sp-settings__hint\">{{ fmt(text.signedInRealm, { realm: accountRealm }) }}</span> }\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [keywords]=\"text.timeZoneKeywords\">\n <span rowDescription class=\"sp-settings__hint\">{{ fmt(text.timeZoneHint, { now: timeZonePreview() }) }}</span>\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n <hm-settings-row [label]=\"text.tour\" [description]=\"text.tourDescription\" [keywords]=\"text.tourKeywords\">\n <hm-button emphasis=\"medium\" icon=\"play\" (clicked)=\"replayTour()\">{{ text.replayTour }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"minimal\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"minimal\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"minimal\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__hint{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-xxxs);padding:var(--hm-space-xxxs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text)}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:Geist Mono,ui-monospace,monospace}.sp-settings__links{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-nano)}.sp-settings__deps{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xxxs);padding:var(--hm-space-nano) var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-hover)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"] }]
2718
- }] });
4287
+ SelectComponent, ShortcutListComponent, ThemePickerComponent, ToggletipComponent,
4288
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.aiAgent). -->\n @if (sessionsItems.aiAgent) {\n <hm-settings-row [label]=\"featureText.aiAgent\" [description]=\"featureText.aiAgentDescription\" [keywords]=\"featureText.aiAgentKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.aiAgent })\" [text]=\"featureText.aiAgentHelp\" />\n <spine-ai-agent />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente) e tour: os dois fecham o painel, que ocupa a tela. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.tour\" [description]=\"text.tourDescription\" [keywords]=\"text.tourKeywords\">\n <hm-button emphasis=\"medium\" icon=\"play\" (clicked)=\"replayTour()\">{{ text.replayTour }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"] }]
4289
+ }], ctorParameters: () => [] });
4290
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2719
4291
  /** "UTC−03:00" para um fuso IANA. */
2720
4292
  function offsetOf(timeZone) {
2721
4293
  try {
@@ -3466,7 +5038,7 @@ class SpineDocsComponent {
3466
5038
  </div>
3467
5039
  </hm-modal>
3468
5040
  }
3469
- `, isInline: true, styles: [".layout{display:grid;grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);gap:var(--hm-space-lg);block-size:min(70vh,48rem);min-block-size:0}.index,.reader{min-block-size:0;overflow-y:auto;overscroll-behavior:contain}.index{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding-inline-end:var(--hm-space-md);border-inline-end:var(--hm-border-hairline) solid var(--hm-divider)}.filter{box-sizing:border-box;inline-size:100%;min-block-size:var(--hm-control-height);margin-block-end:var(--hm-space-sm);padding-inline:var(--hm-space-sm);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm)}.filter::placeholder{color:var(--hm-placeholder)}.filter:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:var(--hm-border-hairline)}.group-title{margin:var(--hm-space-sm) 0 0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);text-transform:uppercase;letter-spacing:.04em}.entries{display:flex;flex-direction:column;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none}.entry{inline-size:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease)}.entry:hover{background:var(--hm-hover)}.entry:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:calc(var(--hm-border-thin) * -1)}.entry--active{background:var(--hm-accent-subtle);color:var(--hm-accent-strong);font-weight:var(--hm-font-weight-semibold)}.reader{display:flex;flex-direction:column;gap:var(--hm-space-md);align-items:stretch}.reader-title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-lg);font-weight:var(--hm-font-weight-semibold)}.reading{animation:spine-docs-enter var(--hm-duration-standard) var(--hm-ease-decelerate)}.note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}@keyframes spine-docs-enter{0%{opacity:0}to{opacity:1}}@media(max-width:767px){.layout{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,2fr) minmax(0,3fr)}.index{padding-inline-end:0;padding-block-end:var(--hm-space-md);border-inline-end:0;border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}}@media(prefers-reduced-motion:reduce){.reading{animation:none}.entry{transition:none}}@media(forced-colors:active){.entry--active{border-color:Highlight}}\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SpineMarkdownComponent, selector: "spine-markdown", inputs: ["content"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5041
+ `, isInline: true, styles: [".layout{display:grid;grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);gap:var(--hm-space-lg);block-size:min(70vh,48rem);min-block-size:0}.index,.reader{min-block-size:0;overflow-y:auto;overscroll-behavior:contain}.index{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding-inline-end:var(--hm-space-md);border-inline-end:var(--hm-border-hairline) solid var(--hm-divider)}.filter{box-sizing:border-box;inline-size:100%;min-block-size:var(--hm-control-height);margin-block-end:var(--hm-space-sm);padding-inline:var(--hm-space-sm);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm)}.filter::placeholder{color:var(--hm-placeholder)}.filter:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:var(--hm-border-hairline)}.group-title{margin:var(--hm-space-sm) 0 0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);text-transform:uppercase;letter-spacing:.04em}.entries{display:flex;flex-direction:column;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none}.entry{inline-size:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease)}.entry:hover{background:var(--hm-hover)}.entry:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:calc(var(--hm-border-thin) * -1)}.entry--active{background:var(--hm-accent-subtle);color:var(--hm-accent-strong);font-weight:var(--hm-font-weight-semibold)}.reader{display:flex;flex-direction:column;gap:var(--hm-space-md);align-items:stretch}.reader-title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-lg);font-weight:var(--hm-font-weight-semibold)}.reading{animation:spine-docs-enter var(--hm-duration-standard) var(--hm-ease-decelerate)}.note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}@keyframes spine-docs-enter{0%{opacity:0}to{opacity:1}}@media(max-width:767px){.layout{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,2fr) minmax(0,3fr)}.index{padding-inline-end:0;padding-block-end:var(--hm-space-md);border-inline-end:0;border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}}@media(prefers-reduced-motion:reduce){.reading{animation:none}.entry{transition:none}}:host-context([data-motion=\"reduce\"]) .reading{animation:none}:host-context([data-motion=\"reduce\"]) .entry{transition:none}@media(forced-colors:active){.entry--active{border-color:Highlight}}\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SpineMarkdownComponent, selector: "spine-markdown", inputs: ["content"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3470
5042
  }
3471
5043
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineDocsComponent, decorators: [{
3472
5044
  type: Component,
@@ -3535,7 +5107,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3535
5107
  </div>
3536
5108
  </hm-modal>
3537
5109
  }
3538
- `, styles: [".layout{display:grid;grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);gap:var(--hm-space-lg);block-size:min(70vh,48rem);min-block-size:0}.index,.reader{min-block-size:0;overflow-y:auto;overscroll-behavior:contain}.index{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding-inline-end:var(--hm-space-md);border-inline-end:var(--hm-border-hairline) solid var(--hm-divider)}.filter{box-sizing:border-box;inline-size:100%;min-block-size:var(--hm-control-height);margin-block-end:var(--hm-space-sm);padding-inline:var(--hm-space-sm);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm)}.filter::placeholder{color:var(--hm-placeholder)}.filter:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:var(--hm-border-hairline)}.group-title{margin:var(--hm-space-sm) 0 0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);text-transform:uppercase;letter-spacing:.04em}.entries{display:flex;flex-direction:column;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none}.entry{inline-size:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease)}.entry:hover{background:var(--hm-hover)}.entry:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:calc(var(--hm-border-thin) * -1)}.entry--active{background:var(--hm-accent-subtle);color:var(--hm-accent-strong);font-weight:var(--hm-font-weight-semibold)}.reader{display:flex;flex-direction:column;gap:var(--hm-space-md);align-items:stretch}.reader-title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-lg);font-weight:var(--hm-font-weight-semibold)}.reading{animation:spine-docs-enter var(--hm-duration-standard) var(--hm-ease-decelerate)}.note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}@keyframes spine-docs-enter{0%{opacity:0}to{opacity:1}}@media(max-width:767px){.layout{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,2fr) minmax(0,3fr)}.index{padding-inline-end:0;padding-block-end:var(--hm-space-md);border-inline-end:0;border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}}@media(prefers-reduced-motion:reduce){.reading{animation:none}.entry{transition:none}}@media(forced-colors:active){.entry--active{border-color:Highlight}}\n"] }]
5110
+ `, styles: [".layout{display:grid;grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);gap:var(--hm-space-lg);block-size:min(70vh,48rem);min-block-size:0}.index,.reader{min-block-size:0;overflow-y:auto;overscroll-behavior:contain}.index{display:flex;flex-direction:column;gap:var(--hm-space-xs);padding-inline-end:var(--hm-space-md);border-inline-end:var(--hm-border-hairline) solid var(--hm-divider)}.filter{box-sizing:border-box;inline-size:100%;min-block-size:var(--hm-control-height);margin-block-end:var(--hm-space-sm);padding-inline:var(--hm-space-sm);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm)}.filter::placeholder{color:var(--hm-placeholder)}.filter:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:var(--hm-border-hairline)}.group-title{margin:var(--hm-space-sm) 0 0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);text-transform:uppercase;letter-spacing:.04em}.entries{display:flex;flex-direction:column;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none}.entry{inline-size:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease)}.entry:hover{background:var(--hm-hover)}.entry:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:calc(var(--hm-border-thin) * -1)}.entry--active{background:var(--hm-accent-subtle);color:var(--hm-accent-strong);font-weight:var(--hm-font-weight-semibold)}.reader{display:flex;flex-direction:column;gap:var(--hm-space-md);align-items:stretch}.reader-title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-lg);font-weight:var(--hm-font-weight-semibold)}.reading{animation:spine-docs-enter var(--hm-duration-standard) var(--hm-ease-decelerate)}.note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}@keyframes spine-docs-enter{0%{opacity:0}to{opacity:1}}@media(max-width:767px){.layout{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,2fr) minmax(0,3fr)}.index{padding-inline-end:0;padding-block-end:var(--hm-space-md);border-inline-end:0;border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}}@media(prefers-reduced-motion:reduce){.reading{animation:none}.entry{transition:none}}:host-context([data-motion=\"reduce\"]) .reading{animation:none}:host-context([data-motion=\"reduce\"]) .entry{transition:none}@media(forced-colors:active){.entry--active{border-color:Highlight}}\n"] }]
3539
5111
  }] });
3540
5112
 
3541
5113
  /**
@@ -3611,6 +5183,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3611
5183
  `, styles: [":host{display:block;flex:none}\n"] }]
3612
5184
  }] });
3613
5185
 
5186
+ /**
5187
+ * Aviso de "Encerrar por inatividade" (`SpineIdleService`): contagem regressiva, "Continuar conectado" (padrão, e o
5188
+ * que fechar pelo X/Esc faz) e "Sair agora". Montado uma vez no `spine-layout`.
5189
+ */
5190
+ class SpineIdleWarningComponent {
5191
+ constructor() {
5192
+ this.idle = inject(SpineIdleService);
5193
+ this.messages = inject(SPINE_MESSAGES);
5194
+ this.text = this.messages.accountFeatures;
5195
+ this.countdown = computed(() => pluralMessage(this.text.idleWarningText, this.idle.secondsLeft(), this.messages.meta.locale), ...(ngDevMode ? [{ debugName: "countdown" }] : /* istanbul ignore next */ []));
5196
+ }
5197
+ onOpenChange(open) {
5198
+ if (!open)
5199
+ this.idle.stay();
5200
+ }
5201
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineIdleWarningComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5202
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineIdleWarningComponent, isStandalone: true, selector: "spine-idle-warning", ngImport: i0, template: `
5203
+ @if (idle.warning()) {
5204
+ <hm-modal [open]="true" [heading]="text.idleWarningHeading" size="small" (openChange)="onOpenChange($event)">
5205
+ <p class="sp-idle__text" role="timer">{{ countdown() }}</p>
5206
+ <div footer class="sp-idle__actions">
5207
+ <hm-button emphasis="medium" icon="logout" (clicked)="idle.logoutNow()">{{ text.idleLogout }}</hm-button>
5208
+ <hm-button emphasis="high" (clicked)="idle.stay()">{{ text.idleStay }}</hm-button>
5209
+ </div>
5210
+ </hm-modal>
5211
+ }
5212
+ `, isInline: true, styles: [".sp-idle__text{margin:0}.sp-idle__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-2)}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5213
+ }
5214
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineIdleWarningComponent, decorators: [{
5215
+ type: Component,
5216
+ args: [{ selector: 'spine-idle-warning', imports: [ButtonComponent, ModalComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
5217
+ @if (idle.warning()) {
5218
+ <hm-modal [open]="true" [heading]="text.idleWarningHeading" size="small" (openChange)="onOpenChange($event)">
5219
+ <p class="sp-idle__text" role="timer">{{ countdown() }}</p>
5220
+ <div footer class="sp-idle__actions">
5221
+ <hm-button emphasis="medium" icon="logout" (clicked)="idle.logoutNow()">{{ text.idleLogout }}</hm-button>
5222
+ <hm-button emphasis="high" (clicked)="idle.stay()">{{ text.idleStay }}</hm-button>
5223
+ </div>
5224
+ </hm-modal>
5225
+ }
5226
+ `, styles: [".sp-idle__text{margin:0}.sp-idle__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-2)}\n"] }]
5227
+ }] });
5228
+
3614
5229
  /**
3615
5230
  * Shell do spine — MESMA estrutura do `docs/website`'s `app.component.html`/`.scss` (decisão de
3616
5231
  * 2026-09-28, ver `hefesto-migration-plan.md`): `.shell` com altura de viewport de verdade
@@ -3620,7 +5235,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3620
5235
  * (`hm-sidebar` fixa + `.main` com `hm-workspace`). **Sem barra de breadcrumb própria** — isso
3621
5236
  * era resíduo do spine antigo (`spine-navigation`), removido: o docs não tem uma, a navegação
3622
5237
  * inteira mora na sidebar. Entre o header e o corpo fica o `spine-connection-banner` (aviso de conexão
3623
- * do `@orfeu/angular`, vazio quando `online`).
5238
+ * do `@orfeu/angular`, vazio quando `online`). Os avisos ficam no `spine-notification` (`NotificationService`) e
5239
+ * no `hm-toast-stack` (`HermesToastService`), os dois montados aqui: o app não monta nenhum.
3624
5240
  *
3625
5241
  * O `<router-outlet>` que sobra aqui é só pra `/no-permission` (a única rota do shell com
3626
5242
  * `component` de verdade — o resto da árvore do backend nunca passa pelo `Router` pra renderizar,
@@ -3630,10 +5246,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3630
5246
  * corressem na mesma volta de detecção de mudanças.
3631
5247
  */
3632
5248
  class LayoutComponent {
3633
- constructor(router, theme, auth, shortcuts, onboarding, news) {
5249
+ constructor(router, theme, shortcuts, onboarding, news) {
3634
5250
  this.router = router;
3635
5251
  this.theme = theme;
3636
- this.auth = auth;
3637
5252
  this.shortcuts = shortcuts;
3638
5253
  this.onboarding = onboarding;
3639
5254
  this.news = news;
@@ -3641,7 +5256,12 @@ class LayoutComponent {
3641
5256
  this.preferences = inject(SpinePreferencesService);
3642
5257
  this.workspace = inject(SpineWorkspaceNavService);
3643
5258
  this.settings = inject(SpineSettingsService);
3644
- this.userStorage = inject(SpineUserStorage);
5259
+ /** Também liga, uma vez, o aviso de falha do logout (`logoutFailed$` do `@orfeu/angular`). */
5260
+ this.logoutService = inject(SpineLogoutService);
5261
+ /** Recursos opcionais (`SpineConfig.features`): cada serviço não faz nada com o seu recurso desligado. */
5262
+ this.idle = inject(SpineIdleService);
5263
+ this.startPage = inject(SpineStartPageService);
5264
+ this.accountPreferences = inject(SpineAccountPreferencesService);
3645
5265
  this.isNoPermission = this.matchesNoPermission(this.router.url);
3646
5266
  this.router.events.pipe(filter((e) => e instanceof NavigationEnd))
3647
5267
  .subscribe(e => { this.isNoPermission = this.matchesNoPermission(e.urlAfterRedirects); });
@@ -3650,15 +5270,16 @@ class LayoutComponent {
3650
5270
  return url.split(/[?#]/)[0] === '/no-permission';
3651
5271
  }
3652
5272
  ngOnInit() {
5273
+ // Preferências na conta (lê e aplica uma vez por sessão do app), inatividade e a "última tela visitada".
5274
+ void this.accountPreferences.start();
5275
+ this.idle.start();
5276
+ this.startPage.track();
3653
5277
  // Liga os atalhos de teclado globais às ações reais do sistema.
3654
5278
  this.shortcuts.setActions({
3655
5279
  navigate: (url) => this.router.navigateByUrl(url),
3656
5280
  toggleTheme: () => this.theme.setTheme(this.theme.current === 'light' ? 'dark' : 'light'),
3657
5281
  toggleFullscreen: () => this.toggleFullscreen(),
3658
- logout: () => {
3659
- this.userStorage.clearHistory();
3660
- this.auth.logout();
3661
- },
5282
+ logout: () => void this.logoutService.logout(),
3662
5283
  openSettings: () => this.settings.toggle(),
3663
5284
  });
3664
5285
  // Link direto pra uma seção do painel de configurações: `?settings=aparencia` (ou só
@@ -3688,7 +5309,7 @@ class LayoutComponent {
3688
5309
  document.exitFullscreen?.();
3689
5310
  }
3690
5311
  }
3691
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LayoutComponent, deps: [{ token: i3.Router }, { token: i2$1.ThemeService }, { token: i1.AuthorizationService }, { token: ShortcutService }, { token: OnboardingService }, { token: SpineNewsService }], target: i0.ɵɵFactoryTarget.Component }); }
5312
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LayoutComponent, deps: [{ token: i3.Router }, { token: i2$1.ThemeService }, { token: ShortcutService }, { token: OnboardingService }, { token: SpineNewsService }], target: i0.ɵɵFactoryTarget.Component }); }
3692
5313
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: LayoutComponent, isStandalone: true, selector: "spine-layout", ngImport: i0, template: `
3693
5314
  <div class="shell">
3694
5315
  <spine-header></spine-header>
@@ -3717,6 +5338,9 @@ class LayoutComponent {
3717
5338
  </div>
3718
5339
  </div>
3719
5340
  <spine-notification></spine-notification>
5341
+ <!-- Avisos do HermesToastService (configurações, conta e segurança, auto-serviço…). Se o app também montar
5342
+ uma, só uma desenha (o serviço escolhe a pilha ativa): nunca em dobro. -->
5343
+ <hm-toast-stack />
3720
5344
  <spine-side-panel />
3721
5345
  <spine-modal />
3722
5346
  <spine-shortcuts-cheatsheet></spine-shortcuts-cheatsheet>
@@ -3724,7 +5348,9 @@ class LayoutComponent {
3724
5348
  <spine-docs />
3725
5349
  <spine-news />
3726
5350
  <spine-onboarding-overlay></spine-onboarding-overlay>
3727
- `, isInline: true, styles: [":host{display:block}.shell{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--hm-surface);color:var(--hm-text)}.body{position:relative;display:flex;flex:1;min-height:0;overflow:hidden}.main{display:flex;flex:1;min-width:0;flex-direction:column}.workspace-area{flex:1;min-height:0;display:flex;flex-direction:column;--hm-tab-strip-bg: var(--hm-surface);--hm-tab-active-bg: var(--hm-surface-raised)}hm-workspace{flex:1;min-height:0}.panel-content{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "component", type: SpineNewsComponent, selector: "spine-news" }, { kind: "component", type: SpineDocsComponent, selector: "spine-docs" }, { kind: "component", type: HeaderComponent, selector: "spine-header" }, { kind: "component", type: SidebarComponent, selector: "spine-sidebar" }, { kind: "component", type: SpineConnectionBannerComponent, selector: "spine-connection-banner" }, { kind: "component", type: SpinePagePanelComponent, selector: "spine-page-panel", inputs: ["url", "isFocusedPanel"] }, { kind: "directive", type: PanelLocalLinkDirective, selector: "[appPanelLocalLink]", inputs: ["appPanelLocalLink"], outputs: ["localNavigate"] }, { kind: "component", type: WorkspaceComponent, selector: "hm-workspace", inputs: ["initialTabs", "initialActiveId", "keepAlive", "label", "providedStore"], outputs: ["stateChange", "newTabRequested"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: ModalComponent$1, selector: "spine-modal" }, { kind: "component", type: NotificationComponent, selector: "spine-notification" }, { kind: "component", type: SidePanelComponent, selector: "spine-side-panel" }, { kind: "component", type: OnboardingOverlayComponent, selector: "spine-onboarding-overlay" }, { kind: "component", type: ShortcutsCheatsheetComponent, selector: "spine-shortcuts-cheatsheet" }, { kind: "component", type: SettingsPanelComponent, selector: "spine-settings-panel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5351
+ <!-- Aviso de "Encerrar por inatividade" (SpineFeatures.idleTimeout): só desenha quando abre. -->
5352
+ <spine-idle-warning />
5353
+ `, isInline: true, styles: [":host{display:block}.shell{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--hm-surface);color:var(--hm-text)}.body{position:relative;display:flex;flex:1;min-height:0;overflow:hidden}.main{display:flex;flex:1;min-width:0;flex-direction:column}.workspace-area{flex:1;min-height:0;display:flex;flex-direction:column;--hm-tab-strip-bg: var(--hm-surface);--hm-tab-active-bg: var(--hm-surface-raised)}hm-workspace{flex:1;min-height:0}.panel-content{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "component", type: SpineNewsComponent, selector: "spine-news" }, { kind: "component", type: SpineDocsComponent, selector: "spine-docs" }, { kind: "component", type: HeaderComponent, selector: "spine-header" }, { kind: "component", type: SidebarComponent, selector: "spine-sidebar" }, { kind: "component", type: SpineConnectionBannerComponent, selector: "spine-connection-banner" }, { kind: "component", type: SpinePagePanelComponent, selector: "spine-page-panel", inputs: ["url", "isFocusedPanel"] }, { kind: "directive", type: PanelLocalLinkDirective, selector: "[appPanelLocalLink]", inputs: ["appPanelLocalLink"], outputs: ["localNavigate"] }, { kind: "component", type: WorkspaceComponent, selector: "hm-workspace", inputs: ["initialTabs", "initialActiveId", "keepAlive", "label", "providedStore"], outputs: ["stateChange", "newTabRequested"] }, { kind: "component", type: ToastStackComponent, selector: "hm-toast-stack" }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: ModalComponent$1, selector: "spine-modal" }, { kind: "component", type: NotificationComponent, selector: "spine-notification" }, { kind: "component", type: SidePanelComponent, selector: "spine-side-panel" }, { kind: "component", type: OnboardingOverlayComponent, selector: "spine-onboarding-overlay" }, { kind: "component", type: ShortcutsCheatsheetComponent, selector: "spine-shortcuts-cheatsheet" }, { kind: "component", type: SettingsPanelComponent, selector: "spine-settings-panel" }, { kind: "component", type: SpineIdleWarningComponent, selector: "spine-idle-warning" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3728
5354
  }
3729
5355
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LayoutComponent, decorators: [{
3730
5356
  type: Component,
@@ -3737,6 +5363,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3737
5363
  SpinePagePanelComponent,
3738
5364
  PanelLocalLinkDirective,
3739
5365
  WorkspaceComponent,
5366
+ ToastStackComponent,
3740
5367
  RouterOutlet,
3741
5368
  ModalComponent$1,
3742
5369
  NotificationComponent,
@@ -3744,6 +5371,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3744
5371
  OnboardingOverlayComponent,
3745
5372
  ShortcutsCheatsheetComponent,
3746
5373
  SettingsPanelComponent,
5374
+ SpineIdleWarningComponent,
3747
5375
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
3748
5376
  <div class="shell">
3749
5377
  <spine-header></spine-header>
@@ -3772,6 +5400,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3772
5400
  </div>
3773
5401
  </div>
3774
5402
  <spine-notification></spine-notification>
5403
+ <!-- Avisos do HermesToastService (configurações, conta e segurança, auto-serviço…). Se o app também montar
5404
+ uma, só uma desenha (o serviço escolhe a pilha ativa): nunca em dobro. -->
5405
+ <hm-toast-stack />
3775
5406
  <spine-side-panel />
3776
5407
  <spine-modal />
3777
5408
  <spine-shortcuts-cheatsheet></spine-shortcuts-cheatsheet>
@@ -3779,12 +5410,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
3779
5410
  <spine-docs />
3780
5411
  <spine-news />
3781
5412
  <spine-onboarding-overlay></spine-onboarding-overlay>
5413
+ <!-- Aviso de "Encerrar por inatividade" (SpineFeatures.idleTimeout): só desenha quando abre. -->
5414
+ <spine-idle-warning />
3782
5415
  `, styles: [":host{display:block}.shell{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--hm-surface);color:var(--hm-text)}.body{position:relative;display:flex;flex:1;min-height:0;overflow:hidden}.main{display:flex;flex:1;min-width:0;flex-direction:column}.workspace-area{flex:1;min-height:0;display:flex;flex-direction:column;--hm-tab-strip-bg: var(--hm-surface);--hm-tab-active-bg: var(--hm-surface-raised)}hm-workspace{flex:1;min-height:0}.panel-content{display:flex;flex-direction:column;height:100%;min-height:0}\n"] }]
3783
- }], ctorParameters: () => [{ type: i3.Router }, { type: i2$1.ThemeService }, { type: i1.AuthorizationService }, { type: ShortcutService }, { type: OnboardingService }, { type: SpineNewsService }] });
5416
+ }], ctorParameters: () => [{ type: i3.Router }, { type: i2$1.ThemeService }, { type: ShortcutService }, { type: OnboardingService }, { type: SpineNewsService }] });
3784
5417
 
3785
5418
  /**
3786
5419
  * Generated bundle index. Do not edit.
3787
5420
  */
3788
5421
 
3789
- export { AppLauncherComponent, HeaderComponent, LayoutComponent, OnboardingOverlayComponent, OnboardingService, SPINE_BRAND, SPINE_MARKDOWN_PURIFY_CONFIG, SPINE_SETTINGS_KEYS, SPINE_SETTINGS_SECTIONS, SettingsPanelComponent, ShortcutService, ShortcutsCheatsheetComponent, SidebarComponent, SpineAccountSecurityService, SpineAppTokensService, SpineBrandComponent, SpineConnectionBannerComponent, SpineDocsComponent, SpineDocsService, SpineMarkdownComponent, SpineNewsComponent, SpineNewsService, SpineSettingsService, accountErrorCode, argosAccountApi, findTourTarget, provideSettingsSection, sanitizeSpineMarkdownHtml };
5422
+ export { AiAgentComponent, AppLauncherComponent, HeaderComponent, LayoutComponent, OnboardingOverlayComponent, OnboardingService, SPINE_BRAND, SPINE_IDLE_WARNING_SECONDS, SPINE_MARKDOWN_PURIFY_CONFIG, SPINE_PREFERENCES_SAVE_DELAY_MS, SPINE_SETTINGS_ALIASES, SPINE_SETTINGS_KEYS, SPINE_SETTINGS_SECTIONS, SettingsPanelComponent, ShortcutService, ShortcutsCheatsheetComponent, SidebarComponent, SpineAccountPreferencesService, SpineAccountSecurityService, SpineAppTokensService, SpineBrandComponent, SpineConnectionBannerComponent, SpineDocsComponent, SpineDocsService, SpineIdleService, SpineIdleWarningComponent, SpineLogoutService, SpineMarkdownComponent, SpineNewsComponent, SpineNewsService, SpineNoticesService, SpineSettingsService, SpineStartPageService, accountErrorCode, argosAccountApi, buildMcpConfig, effectiveIdleMinutes, findTourTarget, idleLimits, isInternalPath, provideSettingsSection, resolveSettingsSection, sanitizeSpineMarkdownHtml, stable };
3790
5423
  //# sourceMappingURL=trismegisto-spine-layout.mjs.map