@ibgib/web-gib 0.0.52 → 0.0.56

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 (150) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  3. package/dist/AUTO-GENERATED-version.mjs +1 -1
  4. package/dist/api/commands/chat/hello-world.mjs.map +1 -1
  5. package/dist/api/commands/command-constants.d.mts +4 -4
  6. package/dist/api/commands/command-constants.d.mts.map +1 -1
  7. package/dist/api/commands/command-service-v1.d.mts.map +1 -1
  8. package/dist/api/commands/command-service-v1.mjs.map +1 -1
  9. package/dist/api/commands/ibgib/get-ibgibs.d.mts.map +1 -1
  10. package/dist/api/commands/ibgib/get-ibgibs.mjs.map +1 -1
  11. package/dist/api/ibgib-api-bridge.d.mts +81 -0
  12. package/dist/api/ibgib-api-bridge.d.mts.map +1 -0
  13. package/dist/api/ibgib-api-bridge.mjs +259 -0
  14. package/dist/api/ibgib-api-bridge.mjs.map +1 -0
  15. package/dist/app-bootstrap/init-orchestration.d.mts +1 -1
  16. package/dist/app-bootstrap/init-orchestration.d.mts.map +1 -1
  17. package/dist/app-bootstrap/init-orchestration.mjs +17 -3
  18. package/dist/app-bootstrap/init-orchestration.mjs.map +1 -1
  19. package/dist/assets/fullscreen-dialog.html +18 -0
  20. package/dist/assumptions.respec.mjs.map +1 -1
  21. package/dist/common/settings/settings-constants.d.mts.map +1 -1
  22. package/dist/helpers.d.mts.map +1 -1
  23. package/dist/helpers.mjs.map +1 -1
  24. package/dist/helpers.web.d.mts.map +1 -1
  25. package/dist/helpers.web.mjs.map +1 -1
  26. package/dist/helpers.web.respec.mjs.map +1 -1
  27. package/dist/identity/keystone-sync-service.d.mts +37 -0
  28. package/dist/identity/keystone-sync-service.d.mts.map +1 -0
  29. package/dist/identity/keystone-sync-service.mjs +211 -0
  30. package/dist/identity/keystone-sync-service.mjs.map +1 -0
  31. package/dist/identity/sso/sso-custodian-service.d.mts.map +1 -1
  32. package/dist/identity/sso/sso-custodian-service.mjs.map +1 -1
  33. package/dist/identity/sso/sso-custodian-service.respec.mjs.map +1 -1
  34. package/dist/index.d.mts +17 -1
  35. package/dist/index.d.mts.map +1 -1
  36. package/dist/index.mjs +21 -26
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/storage/storage-helpers.web.mjs.map +1 -1
  39. package/dist/ui/component/common-component-styles.css +270 -0
  40. package/dist/ui/component/ibgib-component-service.d.mts.map +1 -1
  41. package/dist/ui/component/ibgib-dynamic-component-bases.d.mts.map +1 -1
  42. package/dist/ui/component/ibgib-dynamic-component-bases.mjs.map +1 -1
  43. package/dist/ui/component/identity/add-device/add-device.css +133 -0
  44. package/dist/ui/component/identity/add-device/add-device.d.mts +49 -0
  45. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -0
  46. package/dist/ui/component/identity/add-device/add-device.html +52 -0
  47. package/dist/ui/component/identity/add-device/add-device.mjs +321 -0
  48. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -0
  49. package/dist/ui/component/identity/identity-header/identity-header.css +317 -0
  50. package/dist/ui/component/identity/identity-header/identity-header.d.mts +85 -0
  51. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -0
  52. package/dist/ui/component/identity/identity-header/identity-header.html +72 -0
  53. package/dist/ui/component/identity/identity-header/identity-header.mjs +546 -0
  54. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -0
  55. package/dist/ui/component/identity/identity-manager/identity-manager.css +178 -0
  56. package/dist/ui/component/identity/identity-manager/identity-manager.d.mts +76 -0
  57. package/dist/ui/component/identity/identity-manager/identity-manager.d.mts.map +1 -0
  58. package/dist/ui/component/identity/identity-manager/identity-manager.html +19 -0
  59. package/dist/ui/component/identity/identity-manager/identity-manager.mjs +591 -0
  60. package/dist/ui/component/identity/identity-manager/identity-manager.mjs.map +1 -0
  61. package/dist/ui/component/identity/index.d.mts +17 -0
  62. package/dist/ui/component/identity/index.d.mts.map +1 -0
  63. package/dist/ui/component/identity/index.mjs +34 -0
  64. package/dist/ui/component/identity/index.mjs.map +1 -0
  65. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +97 -0
  66. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +58 -0
  67. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -0
  68. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +51 -0
  69. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +410 -0
  70. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -0
  71. package/dist/ui/component/identity/keystone-details/keystone-details.css +621 -0
  72. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +98 -0
  73. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -0
  74. package/dist/ui/component/identity/keystone-details/keystone-details.html +154 -0
  75. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +1377 -0
  76. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -0
  77. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.css +46 -0
  78. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.d.mts +64 -0
  79. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.d.mts.map +1 -0
  80. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.html +15 -0
  81. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.mjs +300 -0
  82. package/dist/ui/component/identity/keystone-scrubber/keystone-scrubber.mjs.map +1 -0
  83. package/dist/ui/component/identity/remove-device/remove-device.css +118 -0
  84. package/dist/ui/component/identity/remove-device/remove-device.d.mts +50 -0
  85. package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -0
  86. package/dist/ui/component/identity/remove-device/remove-device.html +31 -0
  87. package/dist/ui/component/identity/remove-device/remove-device.mjs +188 -0
  88. package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -0
  89. package/dist/ui/ui-constants.d.mts +1 -0
  90. package/dist/ui/ui-constants.d.mts.map +1 -1
  91. package/dist/ui/ui-constants.mjs +1 -0
  92. package/dist/ui/ui-constants.mjs.map +1 -1
  93. package/dist/ui/ui-types.d.mts +7 -0
  94. package/dist/ui/ui-types.d.mts.map +1 -1
  95. package/dist/witness/agent/agent-constants.d.mts.map +1 -1
  96. package/dist/witness/agent/agent-helpers.mjs.map +1 -1
  97. package/dist/witness/agent/agent-one-file.d.mts.map +1 -1
  98. package/dist/witness/agent/agent-one-file.mjs.map +1 -1
  99. package/dist/witness/agent/agents-service-v1.d.mts.map +1 -1
  100. package/dist/witness/agent/agents-service-v1.mjs.map +1 -1
  101. package/dist/witness/agent/gemini/agent-witness-gemini-v1.d.mts.map +1 -1
  102. package/dist/witness/agent/gemini/gemini-agent-factory.d.mts.map +1 -1
  103. package/dist/witness/live-proxy-ibgib/live-proxy-ibgib-one-file.d.mts.map +1 -1
  104. package/dist/witness/live-proxy-ibgib/live-proxy-ibgib-one-file.mjs.map +1 -1
  105. package/dist/witness/space/metaspace/metaspace-webspace/metaspace-webspace-helper.mjs.map +1 -1
  106. package/dist/witness/space/metaspace/metaspace-webspace/metaspace-webspace.d.mts +4 -0
  107. package/dist/witness/space/metaspace/metaspace-webspace/metaspace-webspace.d.mts.map +1 -1
  108. package/dist/witness/space/metaspace/metaspace-webspace/metaspace-webspace.mjs +40 -1
  109. package/dist/witness/space/metaspace/metaspace-webspace/metaspace-webspace.mjs.map +1 -1
  110. package/dist/witness/space/web-filesystem-space/web-filesystem-space-types.d.mts.map +1 -1
  111. package/dist/witness/space/web-filesystem-space/web-filesystem-space-v1.d.mts.map +1 -1
  112. package/package.json +2 -2
  113. package/src/AUTO-GENERATED-version.mts +1 -1
  114. package/src/api/ibgib-api-bridge.mts +292 -0
  115. package/src/app-bootstrap/init-orchestration.mts +17 -6
  116. package/src/custom.d.ts +11 -0
  117. package/src/identity/keystone-sync-service.mts +257 -0
  118. package/src/index.mts +24 -25
  119. package/src/ui/component/README.md +82 -5
  120. package/src/ui/component/common-component-styles.css +270 -0
  121. package/src/ui/component/identity/add-device/add-device.css +133 -0
  122. package/src/ui/component/identity/add-device/add-device.html +52 -0
  123. package/src/ui/component/identity/add-device/add-device.mts +397 -0
  124. package/src/ui/component/identity/identity-header/IMPLEMENTATION.md +45 -0
  125. package/src/ui/component/identity/identity-header/identity-header.css +317 -0
  126. package/src/ui/component/identity/identity-header/identity-header.html +72 -0
  127. package/src/ui/component/identity/identity-header/identity-header.mts +651 -0
  128. package/src/ui/component/identity/identity-manager/IMPLEMENTATION.md +100 -0
  129. package/src/ui/component/identity/identity-manager/identity-manager.css +178 -0
  130. package/src/ui/component/identity/identity-manager/identity-manager.html +19 -0
  131. package/src/ui/component/identity/identity-manager/identity-manager.mts +675 -0
  132. package/src/ui/component/identity/index.mts +36 -0
  133. package/src/ui/component/identity/keystone-creator/keystone-creator.css +97 -0
  134. package/src/ui/component/identity/keystone-creator/keystone-creator.html +51 -0
  135. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +497 -0
  136. package/src/ui/component/identity/keystone-details/keystone-details.css +621 -0
  137. package/src/ui/component/identity/keystone-details/keystone-details.html +154 -0
  138. package/src/ui/component/identity/keystone-details/keystone-details.mts +1506 -0
  139. package/src/ui/component/identity/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +33 -0
  140. package/src/ui/component/identity/keystone-scrubber/keystone-scrubber.css +46 -0
  141. package/src/ui/component/identity/keystone-scrubber/keystone-scrubber.html +15 -0
  142. package/src/ui/component/identity/keystone-scrubber/keystone-scrubber.mts +355 -0
  143. package/src/ui/component/identity/remove-device/remove-device.css +118 -0
  144. package/src/ui/component/identity/remove-device/remove-device.html +31 -0
  145. package/src/ui/component/identity/remove-device/remove-device.mts +247 -0
  146. package/src/ui/ui-constants.mts +1 -0
  147. package/src/ui/ui-types.mts +8 -0
  148. package/src/witness/space/metaspace/metaspace-webspace/metaspace-webspace.mts +42 -1
  149. package/tools/auto-generated-agent-skills/skills/ibgib-dependency-graph/SKILL.md +209 -0
  150. package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +102 -0
@@ -0,0 +1,651 @@
1
+ import thisCss from "./identity-header.css";
2
+ import thisHtml from "./identity-header.html";
3
+ import commonComponentStylesCss from "../../common-component-styles.css";
4
+
5
+ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
6
+ import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
7
+ import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
8
+ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
+ import {
10
+ IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
11
+ } from "../../ibgib-dynamic-component-bases.mjs";
12
+ import {
13
+ ElementsBase, IbGibDynamicComponentInstance,
14
+ IbGibDynamicComponentInstanceInitOpts,
15
+ } from "../../component-types.mjs";
16
+ import { SettingsType } from "../../../../common/settings/settings-constants.mjs";
17
+ import { Settings_General } from "../../../../common/settings/settings-types.mjs";
18
+ import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
19
+ import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
20
+ import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
21
+ import { getDeterministicColorInfo, getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
22
+ import {
23
+ EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED, EVENT_IBGIB_UI_MESSAGE,
24
+ EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE,
25
+ } from "../../../ui-constants.mjs";
26
+ import { EventIbGibIdentityUpdateAvailableDetail } from "../../../ui-types.mjs";
27
+ import { getKeystoneSyncService } from "../../../../identity/keystone-sync-service.mjs";
28
+
29
+ export const IDENTITY_HEADER_COMPONENT_NAME = 'ibgib-identity-header';
30
+
31
+ /**
32
+ * Metadata for the Identity Header component.
33
+ */
34
+ export class IdentityHeaderComponentMeta extends IbGibDynamicComponentMetaBase {
35
+ protected lc: string = `[IdentityHeaderComponentMeta]`;
36
+
37
+ routeRegExp?: RegExp = new RegExp(`^${IDENTITY_HEADER_COMPONENT_NAME}$`);
38
+ componentName = IDENTITY_HEADER_COMPONENT_NAME;
39
+
40
+ constructor() {
41
+ super(getComponentCtorArg());
42
+ if (!customElements.get(this.componentName)) {
43
+ customElements.define(this.componentName, IdentityHeaderComponentInstance);
44
+ }
45
+ }
46
+
47
+ async createInstance({
48
+ path,
49
+ ibGibAddr
50
+ }: {
51
+ path: string;
52
+ ibGibAddr: IbGibAddr;
53
+ }): Promise<IbGibDynamicComponentInstance> {
54
+ const component = document.createElement(this.componentName) as IdentityHeaderComponentInstance;
55
+ await component.initialize({
56
+ ibGibAddr,
57
+ meta: this,
58
+ html: thisHtml,
59
+ css: [commonComponentStylesCss, thisCss],
60
+ });
61
+ return component;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * References to DOM elements within the component's shadow root.
67
+ */
68
+ interface IdentityHeaderElements extends ElementsBase {
69
+ containerEl: HTMLDivElement;
70
+ btnIdentityPillEl: HTMLButtonElement;
71
+ pillAuthContentEl: HTMLDivElement;
72
+ pillUnauthContentEl: HTMLDivElement;
73
+ identityAvatarEl: HTMLDivElement;
74
+ identityUsernameEl: HTMLSpanElement;
75
+ updateBadgeEl: HTMLSpanElement;
76
+
77
+ dropdownMenuEl: HTMLDivElement;
78
+
79
+ sectionUpdateEl: HTMLDivElement;
80
+ btnMenuUpdateIdentityEl: HTMLButtonElement;
81
+ updateNTextEl: HTMLSpanElement;
82
+
83
+ sectionIdentitiesEl: HTMLDivElement;
84
+ identitiesListEl: HTMLDivElement;
85
+
86
+ sectionActionsEl: HTMLDivElement;
87
+ btnMenuCreateAccountEl: HTMLButtonElement;
88
+ btnMenuSignInEl: HTMLButtonElement;
89
+ btnMenuGoogleSsoEl: HTMLButtonElement;
90
+ btnMenuGithubSsoEl: HTMLButtonElement;
91
+
92
+ sectionSettingsEl: HTMLDivElement;
93
+ btnMenuManageEl: HTMLButtonElement;
94
+
95
+ syncProgressModalEl: HTMLDivElement;
96
+ syncModalSpinnerEl: HTMLDivElement;
97
+ syncModalTitleEl: HTMLHeadingElement;
98
+ syncModalMsgEl: HTMLParagraphElement;
99
+ syncModalStatusEl: HTMLDivElement;
100
+ syncModalNTextEl: HTMLSpanElement;
101
+ btnSyncModalCloseEl: HTMLButtonElement;
102
+ }
103
+
104
+ /**
105
+ * Concrete implementation of the Identity Header component.
106
+ */
107
+ export class IdentityHeaderComponentInstance
108
+ extends IbGibDynamicComponentInstanceBase<IbGib_V1, IdentityHeaderElements>
109
+ implements IbGibDynamicComponentInstance<IbGib_V1, IdentityHeaderElements> {
110
+
111
+ protected lc: string = `[IdentityHeaderComponentInstance]`;
112
+ private activeAddr: string | null = null;
113
+ private _onRequestIdentityChange: ((e: any) => void) | null = null;
114
+ private _onIdentityUpdateAvailable: ((e: any) => void) | null = null;
115
+ private _onIdentityChanged: ((e: any) => void) | null = null;
116
+ private _onDocumentClick: ((e: MouseEvent) => void) | null = null;
117
+ private pendingUpdateDetail: EventIbGibIdentityUpdateAvailableDetail | null = null;
118
+ private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean }> = new Map();
119
+ private isDropdownLocked = false;
120
+ private dropdownHideTimer: any = null;
121
+
122
+ constructor() {
123
+ super();
124
+ }
125
+
126
+ private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string, isPrimary: boolean }> {
127
+ const cached = this.keystoneCache.get(addr);
128
+ if (cached) return cached;
129
+
130
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
131
+ if (!metaspace) return { username: '', description: '', isPrimary: false };
132
+ const space = await metaspace.getLocalUserSpace({ lock: false });
133
+ if (!space) return { username: '', description: '', isPrimary: false };
134
+
135
+ try {
136
+ const keystoneSvc = new KeystoneService_V1();
137
+ const info = await keystoneSvc.getKeystoneCommonInfo({
138
+ addr,
139
+ metaspace,
140
+ space,
141
+ });
142
+ const details = {
143
+ username: info?.name ?? '',
144
+ description: info?.description ?? '',
145
+ isPrimary: info?.isPrimary ?? false
146
+ };
147
+ this.keystoneCache.set(addr, details);
148
+ return details;
149
+ } catch {
150
+ return { username: '', description: '', isPrimary: false };
151
+ }
152
+ }
153
+
154
+ override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
155
+ await super.initialize(opts);
156
+ }
157
+
158
+ override async created(): Promise<void> {
159
+ this.elements = {} as IdentityHeaderElements;
160
+ this.elements.containerEl = this.shadowRoot!.getElementById('container') as HTMLDivElement;
161
+ this.elements.btnIdentityPillEl = this.shadowRoot!.getElementById('btn-identity-pill') as HTMLButtonElement;
162
+ this.elements.pillAuthContentEl = this.shadowRoot!.getElementById('pill-auth-content') as HTMLDivElement;
163
+ this.elements.pillUnauthContentEl = this.shadowRoot!.getElementById('pill-unauth-content') as HTMLDivElement;
164
+ this.elements.identityAvatarEl = this.shadowRoot!.getElementById('identity-avatar') as HTMLDivElement;
165
+ this.elements.identityUsernameEl = this.shadowRoot!.getElementById('identity-username') as HTMLSpanElement;
166
+ this.elements.updateBadgeEl = this.shadowRoot!.getElementById('update-badge') as HTMLSpanElement;
167
+
168
+ this.elements.dropdownMenuEl = this.shadowRoot!.getElementById('dropdown-menu') as HTMLDivElement;
169
+
170
+ this.elements.sectionUpdateEl = this.shadowRoot!.getElementById('section-update') as HTMLDivElement;
171
+ this.elements.btnMenuUpdateIdentityEl = this.shadowRoot!.getElementById('btn-menu-update-identity') as HTMLButtonElement;
172
+ this.elements.updateNTextEl = this.shadowRoot!.getElementById('update-n-text') as HTMLSpanElement;
173
+
174
+ this.elements.sectionIdentitiesEl = this.shadowRoot!.getElementById('section-identities') as HTMLDivElement;
175
+ this.elements.identitiesListEl = this.shadowRoot!.getElementById('identities-list') as HTMLDivElement;
176
+
177
+ this.elements.sectionActionsEl = this.shadowRoot!.getElementById('section-actions') as HTMLDivElement;
178
+ this.elements.btnMenuCreateAccountEl = this.shadowRoot!.getElementById('btn-menu-create-account') as HTMLButtonElement;
179
+ this.elements.btnMenuSignInEl = this.shadowRoot!.getElementById('btn-menu-sign-in') as HTMLButtonElement;
180
+ this.elements.btnMenuGoogleSsoEl = this.shadowRoot!.getElementById('btn-menu-google-sso') as HTMLButtonElement;
181
+ this.elements.btnMenuGithubSsoEl = this.shadowRoot!.getElementById('btn-menu-github-sso') as HTMLButtonElement;
182
+
183
+ this.elements.sectionSettingsEl = this.shadowRoot!.getElementById('section-settings') as HTMLDivElement;
184
+ this.elements.btnMenuManageEl = this.shadowRoot!.getElementById('btn-menu-manage') as HTMLButtonElement;
185
+
186
+ this.elements.syncProgressModalEl = this.shadowRoot!.getElementById('sync-progress-modal') as HTMLDivElement;
187
+ this.elements.syncModalSpinnerEl = this.shadowRoot!.getElementById('sync-modal-spinner') as HTMLDivElement;
188
+ this.elements.syncModalTitleEl = this.shadowRoot!.getElementById('sync-modal-title') as HTMLHeadingElement;
189
+ this.elements.syncModalMsgEl = this.shadowRoot!.getElementById('sync-modal-msg') as HTMLParagraphElement;
190
+ this.elements.syncModalStatusEl = this.shadowRoot!.getElementById('sync-modal-status') as HTMLDivElement;
191
+ this.elements.syncModalNTextEl = this.shadowRoot!.getElementById('sync-modal-n-text') as HTMLSpanElement;
192
+ this.elements.btnSyncModalCloseEl = this.shadowRoot!.getElementById('btn-sync-modal-close') as HTMLButtonElement;
193
+
194
+ this.elements.contentEl = this.elements.containerEl;
195
+
196
+ this.initHandlers();
197
+ await this.loadKeystonesIndex();
198
+
199
+ this._onRequestIdentityChange = (e: any) => this.handleRequestIdentityChange(e);
200
+ this._onIdentityUpdateAvailable = (e: any) => this.handleIdentityUpdateAvailable(e);
201
+ this._onIdentityChanged = (e: any) => this.handleIdentityChanged(e);
202
+ this._onDocumentClick = (e: MouseEvent) => {
203
+ const path = e.composedPath ? e.composedPath() : [];
204
+ if (this.elements?.containerEl && !path.includes(this.elements.containerEl)) {
205
+ this.closeDropdown();
206
+ }
207
+ };
208
+
209
+ window.addEventListener(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, this._onRequestIdentityChange);
210
+ window.addEventListener(EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE, this._onIdentityUpdateAvailable);
211
+ window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
212
+ document.addEventListener('click', this._onDocumentClick);
213
+
214
+ await this.resolveActiveIdentity();
215
+ await this.renderUI();
216
+ }
217
+
218
+ private async loadKeystonesIndex(): Promise<void> {
219
+ const lc = `${this.lc}[loadKeystonesIndex]`;
220
+ try {
221
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
222
+ if (!metaspace) { return; }
223
+ const space = await metaspace.getLocalUserSpace({ lock: false });
224
+ if (!space) { return; }
225
+ const keystonesIndex = await metaspace.getSpecialIbGib({
226
+ type: "keystones",
227
+ space,
228
+ initialize: true,
229
+ });
230
+ if (keystonesIndex) {
231
+ this.ibGibAddr = getIbGibAddr({ ibGib: keystonesIndex });
232
+ await this.loadIbGib();
233
+ await this.initSettings();
234
+ }
235
+ } catch (error) {
236
+ console.error(`${lc} ${extractErrorMsg(error)}`);
237
+ }
238
+ }
239
+
240
+ override async disconnected(): Promise<void> {
241
+ if (this._onRequestIdentityChange) {
242
+ window.removeEventListener(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, this._onRequestIdentityChange);
243
+ }
244
+ if (this._onIdentityUpdateAvailable) {
245
+ window.removeEventListener(EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE, this._onIdentityUpdateAvailable);
246
+ }
247
+ if (this._onIdentityChanged) {
248
+ window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
249
+ }
250
+ if (this._onDocumentClick) {
251
+ document.removeEventListener('click', this._onDocumentClick);
252
+ }
253
+ }
254
+
255
+ private closeDropdown() {
256
+ if (this.dropdownHideTimer) {
257
+ clearTimeout(this.dropdownHideTimer);
258
+ this.dropdownHideTimer = null;
259
+ }
260
+ this.isDropdownLocked = false;
261
+ this.elements?.btnIdentityPillEl.classList.remove('locked');
262
+ this.elements?.dropdownMenuEl.classList.add('hidden');
263
+ }
264
+
265
+ private initHandlers() {
266
+ // Hover mechanics for header widget container
267
+ this.elements!.containerEl.addEventListener('mouseenter', () => {
268
+ if (this.dropdownHideTimer) {
269
+ clearTimeout(this.dropdownHideTimer);
270
+ this.dropdownHideTimer = null;
271
+ }
272
+ this.elements!.dropdownMenuEl.classList.remove('hidden');
273
+ });
274
+ this.elements!.containerEl.addEventListener('mouseleave', () => {
275
+ if (!this.isDropdownLocked) {
276
+ this.dropdownHideTimer = setTimeout(() => {
277
+ this.elements?.dropdownMenuEl.classList.add('hidden');
278
+ this.dropdownHideTimer = null;
279
+ }, 300);
280
+ }
281
+ });
282
+
283
+ // Click on top control pill button ONLY toggles dropdown menu. NEVER executes identity update directly!
284
+ this.elements!.btnIdentityPillEl.addEventListener('click', (e) => {
285
+ e.stopPropagation();
286
+ if (this.dropdownHideTimer) {
287
+ clearTimeout(this.dropdownHideTimer);
288
+ this.dropdownHideTimer = null;
289
+ }
290
+ this.isDropdownLocked = !this.isDropdownLocked;
291
+ if (this.isDropdownLocked) {
292
+ this.elements!.btnIdentityPillEl.classList.add('locked');
293
+ this.elements!.dropdownMenuEl.classList.remove('hidden');
294
+ } else {
295
+ this.elements!.btnIdentityPillEl.classList.remove('locked');
296
+ this.elements!.dropdownMenuEl.classList.add('hidden');
297
+ }
298
+ });
299
+
300
+ // Modal close button handler
301
+ this.elements!.btnSyncModalCloseEl.addEventListener('click', (e) => {
302
+ e.stopPropagation();
303
+ this.elements?.syncProgressModalEl.classList.add('hidden');
304
+ });
305
+
306
+ // Section 1: Update Identity Button Handler
307
+ this.elements!.btnMenuUpdateIdentityEl.addEventListener('click', async (e) => {
308
+ e.stopPropagation();
309
+ if (!this.activeAddr) return;
310
+ const lc = `${this.lc}[btnMenuUpdateIdentityEl.click]`;
311
+
312
+ // 1. Immediately call closeDropdown()
313
+ this.closeDropdown();
314
+
315
+ // 2. Open #sync-progress-modal displaying spinner 🔄, title, message, status n: localN ➔ serverN
316
+ const localN = this.pendingUpdateDetail?.localN ?? 0;
317
+ const serverN = this.pendingUpdateDetail?.serverN ?? 0;
318
+
319
+ if (this.elements) {
320
+ this.elements.syncModalSpinnerEl.textContent = '🔄';
321
+ this.elements.syncModalSpinnerEl.style.animation = 'spin-pulse 2s infinite ease-in-out';
322
+ this.elements.syncModalTitleEl.textContent = 'Updating Identity Keystone...';
323
+ this.elements.syncModalMsgEl.textContent = 'Fetching delta graph from server and fast-forwarding local space...';
324
+ this.elements.syncModalNTextEl.textContent = `n: ${localN} ➔ ${serverN}`;
325
+ this.elements.btnSyncModalCloseEl.classList.add('hidden');
326
+ this.elements.syncProgressModalEl.classList.remove('hidden');
327
+ }
328
+
329
+ try {
330
+ const syncSvc = getKeystoneSyncService();
331
+ // 3. Await syncSvc.updateActiveKeystone
332
+ await syncSvc.updateActiveKeystone({ activePrimaryAddr: this.activeAddr });
333
+
334
+ // 4. On Success:
335
+ if (this.elements) {
336
+ this.elements.syncModalSpinnerEl.textContent = '✅';
337
+ this.elements.syncModalSpinnerEl.style.animation = 'none';
338
+ this.elements.syncModalTitleEl.textContent = 'Identity Updated!';
339
+ this.elements.syncModalMsgEl.textContent = 'Successfully fast-forwarded identity keystone.';
340
+ this.elements.btnSyncModalCloseEl.textContent = 'Done';
341
+ this.elements.btnSyncModalCloseEl.classList.remove('hidden');
342
+ }
343
+
344
+ this.pendingUpdateDetail = null;
345
+ if (this.elements) {
346
+ this.elements.updateBadgeEl.classList.add('hidden');
347
+ this.elements.sectionUpdateEl.classList.add('hidden');
348
+ }
349
+
350
+ setTimeout(() => {
351
+ this.elements?.syncProgressModalEl.classList.add('hidden');
352
+ }, 1500);
353
+
354
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
355
+ detail: { activeIdentityAddr: this.activeAddr },
356
+ bubbles: true,
357
+ composed: true
358
+ }));
359
+ } catch (error) {
360
+ console.error(`${lc} ${extractErrorMsg(error)}`);
361
+ // 5. On Error:
362
+ if (this.elements) {
363
+ this.elements.syncModalSpinnerEl.textContent = '❌';
364
+ this.elements.syncModalSpinnerEl.style.animation = 'none';
365
+ this.elements.syncModalTitleEl.textContent = 'Update Failed';
366
+ this.elements.syncModalMsgEl.textContent = extractErrorMsg(error);
367
+ this.elements.btnSyncModalCloseEl.textContent = 'Close';
368
+ this.elements.btnSyncModalCloseEl.classList.remove('hidden');
369
+ }
370
+ }
371
+ });
372
+
373
+ // Section 3 Actions
374
+ this.elements!.btnMenuCreateAccountEl.addEventListener('click', (e) => {
375
+ e.stopPropagation();
376
+ this.closeDropdown();
377
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
378
+ detail: { action: 'show-keystone-creator' },
379
+ bubbles: true,
380
+ composed: true
381
+ }));
382
+ });
383
+
384
+ this.elements!.btnMenuSignInEl.addEventListener('click', (e) => {
385
+ e.stopPropagation();
386
+ this.closeDropdown();
387
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
388
+ detail: { action: 'show-add-device' },
389
+ bubbles: true,
390
+ composed: true
391
+ }));
392
+ });
393
+
394
+ this.elements!.btnMenuGoogleSsoEl.addEventListener('click', (e) => {
395
+ e.stopPropagation();
396
+ this.closeDropdown();
397
+ });
398
+
399
+ this.elements!.btnMenuGithubSsoEl.addEventListener('click', (e) => {
400
+ e.stopPropagation();
401
+ this.closeDropdown();
402
+ });
403
+
404
+ // Section 4 Settings: Identity Manager
405
+ this.elements!.btnMenuManageEl.addEventListener('click', (e) => {
406
+ e.stopPropagation();
407
+ this.closeDropdown();
408
+ this.navigateToManager();
409
+ });
410
+ }
411
+
412
+ private navigateToManager() {
413
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
414
+ detail: { action: 'show-identity-manager' },
415
+ bubbles: true,
416
+ composed: true
417
+ }));
418
+ }
419
+
420
+ /**
421
+ * Resolves the active identity from settings and verifies it exists in the keystones index.
422
+ */
423
+ private async resolveActiveIdentity() {
424
+ const lc = `${this.lc}[resolveActiveIdentity]`;
425
+ try {
426
+ const globalState = (globalThis as any).ibgib ?? {};
427
+ if (!this.settings) {
428
+ this.activeAddr = null;
429
+ globalState.identity = undefined;
430
+ return;
431
+ }
432
+ const generalSettings = await this.getSettings<Settings_General>({
433
+ settingsType: SettingsType.general,
434
+ useCase: 'current',
435
+ });
436
+ const activeAddrInSettings = (generalSettings as any)?.activeIdentityAddr;
437
+
438
+ // Verify it is actually one of the registered keystones in the index relations
439
+ const registeredKeystones = this.ibGib?.rel8ns?.keystone ?? [];
440
+ const detailsPromises = registeredKeystones.map(async addr => {
441
+ const details = await this.getKeystoneDetails(addr);
442
+ return { addr, isPrimary: details?.isPrimary ?? false };
443
+ });
444
+ const resolvedDetails = await Promise.all(detailsPromises);
445
+ const primaryKeystones = resolvedDetails
446
+ .filter(item => item.isPrimary)
447
+ .map(item => item.addr);
448
+
449
+ if (activeAddrInSettings && primaryKeystones.includes(activeAddrInSettings)) {
450
+ this.activeAddr = activeAddrInSettings;
451
+ } else if (primaryKeystones.length > 0) {
452
+ this.activeAddr = primaryKeystones[0];
453
+ } else {
454
+ this.activeAddr = null;
455
+ }
456
+
457
+ // Sync with globalState.identity
458
+ if (this.activeAddr) {
459
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
460
+ if (metaspace) {
461
+ const space = await metaspace.getLocalUserSpace({ lock: false });
462
+ if (space) {
463
+ const res = await metaspace.get({ addr: this.activeAddr, space });
464
+ if (res.success && res.ibGibs && res.ibGibs.length > 0) {
465
+ const domainIdentity = res.ibGibs[0] as KeystoneIbGib_V1;
466
+
467
+ // Find delegate with the sync allowed verb
468
+ let syncDelegateIdentity: KeystoneIbGib_V1 | undefined;
469
+ const delegates = domainIdentity.data?.delegates;
470
+ if (delegates) {
471
+ for (const tjpAddr of Object.keys(delegates)) {
472
+ const info = delegates[tjpAddr];
473
+ if (info.allowedVerbs?.includes('sync')) {
474
+ const delRes = await metaspace.get({ addr: info.delegateAddr, space });
475
+ if (delRes.success && delRes.ibGibs && delRes.ibGibs.length > 0) {
476
+ syncDelegateIdentity = delRes.ibGibs[0] as KeystoneIbGib_V1;
477
+ break;
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ globalState.identity = {
484
+ domainIdentity,
485
+ syncDelegateIdentity,
486
+ tjpAddr: this.activeAddr
487
+ };
488
+ }
489
+ }
490
+ }
491
+ } else {
492
+ globalState.identity = undefined;
493
+ }
494
+
495
+ } catch (error) {
496
+ console.error(`${lc} ${extractErrorMsg(error)}`);
497
+ this.activeAddr = null;
498
+ }
499
+ }
500
+
501
+ protected override async renderUI() {
502
+ if (!this.elements) return;
503
+
504
+ const registeredKeystones = this.ibGib?.rel8ns?.keystone ?? [];
505
+
506
+ if (this.activeAddr) {
507
+ const addr = this.activeAddr;
508
+ this.elements.btnIdentityPillEl.classList.remove('unauth');
509
+ this.elements.pillAuthContentEl.classList.remove('hidden');
510
+ this.elements.pillUnauthContentEl.classList.add('hidden');
511
+
512
+ // Render active avatar & username
513
+ const details = await this.getKeystoneDetails(addr);
514
+ const username = details.username || addr.substring(0, 10) + '...';
515
+ this.elements.identityUsernameEl.textContent = username;
516
+
517
+ const letter = details.username ? details.username.charAt(0).toUpperCase() : 'I';
518
+ this.elements.identityAvatarEl.textContent = letter;
519
+
520
+ const colorInfo = getDeterministicColorInfo({ ibGibAddr: addr });
521
+ this.elements.identityAvatarEl.style.backgroundColor = colorInfo.tjpColor || colorInfo.punctiliarColor;
522
+ this.elements.identityAvatarEl.style.color = colorInfo.tjpColorContrast || colorInfo.punctiliarColorContrast;
523
+
524
+ } else {
525
+ this.elements.btnIdentityPillEl.classList.add('unauth');
526
+ this.elements.pillAuthContentEl.classList.add('hidden');
527
+ this.elements.pillUnauthContentEl.classList.remove('hidden');
528
+ }
529
+
530
+ // Section 1: Render Update Affordance if update is available
531
+ if (this.pendingUpdateDetail && this.pendingUpdateDetail.hasUpdate && (!this.pendingUpdateDetail.activePrimaryAddr || this.pendingUpdateDetail.activePrimaryAddr === this.activeAddr)) {
532
+ this.elements.updateBadgeEl.classList.remove('hidden');
533
+ this.elements.sectionUpdateEl.classList.remove('hidden');
534
+ if (this.elements.updateNTextEl) {
535
+ this.elements.updateNTextEl.textContent = `n: ${this.pendingUpdateDetail.localN} ➔ ${this.pendingUpdateDetail.serverN}`;
536
+ }
537
+ } else {
538
+ this.elements.updateBadgeEl.classList.add('hidden');
539
+ this.elements.sectionUpdateEl.classList.add('hidden');
540
+ }
541
+
542
+ // Section 2: Render Local Identities List
543
+ this.elements.identitiesListEl.innerHTML = '';
544
+ const primaryKeystones: { addr: string; username: string }[] = [];
545
+ for (const kAddr of registeredKeystones) {
546
+ const details = await this.getKeystoneDetails(kAddr);
547
+ if (details.isPrimary) {
548
+ primaryKeystones.push({
549
+ addr: kAddr,
550
+ username: details.username || kAddr.substring(0, 10) + '...'
551
+ });
552
+ }
553
+ }
554
+
555
+ if (primaryKeystones.length > 0) {
556
+ this.elements.sectionIdentitiesEl.classList.remove('hidden');
557
+ for (const item of primaryKeystones) {
558
+ const isActive = item.addr === this.activeAddr;
559
+ const btn = document.createElement('button');
560
+ btn.className = `dropdown-item identity-item ${isActive ? 'active-identity' : ''}`;
561
+ btn.innerHTML = `
562
+ <span class="icon">${isActive ? '🟢' : '⚪'}</span>
563
+ <span class="identity-name">${item.username}</span>
564
+ ${isActive ? '<span class="identity-badge-active">Active</span>' : ''}
565
+ `;
566
+ btn.addEventListener('click', (e) => {
567
+ e.stopPropagation();
568
+ this.closeDropdown();
569
+ if (!isActive) {
570
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
571
+ detail: { activeIdentityAddr: item.addr },
572
+ bubbles: true,
573
+ composed: true
574
+ }));
575
+ }
576
+ });
577
+ this.elements.identitiesListEl.appendChild(btn);
578
+ }
579
+ } else {
580
+ this.elements.sectionIdentitiesEl.classList.add('hidden');
581
+ }
582
+ }
583
+
584
+ private handleIdentityUpdateAvailable(e: any) {
585
+ const detail = e.detail as EventIbGibIdentityUpdateAvailableDetail;
586
+ if (!detail) return;
587
+
588
+ if (detail.hasUpdate && (!detail.activePrimaryAddr || detail.activePrimaryAddr === this.activeAddr)) {
589
+ this.pendingUpdateDetail = detail;
590
+ if (this.elements) {
591
+ this.elements.updateBadgeEl?.classList.remove('hidden');
592
+ this.elements.sectionUpdateEl?.classList.remove('hidden');
593
+ if (this.elements.updateNTextEl) {
594
+ this.elements.updateNTextEl.textContent = `n: ${detail.localN} ➔ ${detail.serverN}`;
595
+ }
596
+ }
597
+ } else {
598
+ this.pendingUpdateDetail = null;
599
+ if (this.elements) {
600
+ this.elements.updateBadgeEl?.classList.add('hidden');
601
+ this.elements.sectionUpdateEl?.classList.add('hidden');
602
+ }
603
+ }
604
+ }
605
+
606
+ private async handleIdentityChanged(e: any) {
607
+ this.pendingUpdateDetail = null;
608
+ if (this.elements) {
609
+ this.elements.updateBadgeEl?.classList.add('hidden');
610
+ this.elements.sectionUpdateEl?.classList.add('hidden');
611
+ }
612
+ await this.resolveActiveIdentity();
613
+ await this.renderUI();
614
+ }
615
+
616
+ private async handleRequestIdentityChange(e: any) {
617
+ const lc = `${this.lc}[handleRequestIdentityChange]`;
618
+ const addr = e.detail?.activeIdentityAddr;
619
+ if (!addr) { return; }
620
+ try {
621
+ if (!this.settings) { return; }
622
+ const generalSettings = await this.getSettings<Settings_General>({
623
+ settingsType: SettingsType.general,
624
+ useCase: 'current',
625
+ }) ?? { type: 'general' };
626
+
627
+ (generalSettings as any).activeIdentityAddr = addr;
628
+
629
+ await this.updateSettings({
630
+ settingsType: SettingsType.general,
631
+ useCase: 'current',
632
+ newSectionInfo: generalSettings,
633
+ });
634
+
635
+ await this.resolveActiveIdentity();
636
+ await this.renderUI();
637
+
638
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
639
+ detail: { activeIdentityAddr: addr },
640
+ bubbles: true,
641
+ composed: true
642
+ }));
643
+ } catch (error) {
644
+ console.error(`${lc} ${extractErrorMsg(error)}`);
645
+ }
646
+ }
647
+
648
+ override async handleContextUpdated(): Promise<void> {
649
+ await this.renderUI();
650
+ }
651
+ }