@ibgib/space-gib 0.0.6 → 0.0.8

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 (38) hide show
  1. package/README.md +20 -0
  2. package/dist/client/bootstrap.mjs +31 -31
  3. package/dist/client/bootstrap.mjs.map +3 -3
  4. package/dist/client/chunk-DBHMHCGD.mjs +2341 -0
  5. package/dist/client/{chunk-SMOZ2D5E.mjs.map → chunk-DBHMHCGD.mjs.map} +4 -4
  6. package/dist/client/chunk-SUQ5QJH4.mjs +42 -0
  7. package/dist/client/chunk-SUQ5QJH4.mjs.map +7 -0
  8. package/dist/client/index.mjs +1 -1
  9. package/dist/client/script.mjs +1 -1
  10. package/dist/server/server.mjs +1561 -179
  11. package/dist/server/server.mjs.map +4 -4
  12. package/package.json +5 -5
  13. package/space-gib.localhost-1783713259760.log +45 -0
  14. package/src/client/AUTO-GENERATED-version.mts +1 -1
  15. package/src/client/api/space-gib-api-bridge.mts +7 -2
  16. package/src/client/bootstrap.mts +9 -0
  17. package/src/client/components/identity-header/identity-header.mts +43 -26
  18. package/src/client/components/identity-manager/identity-manager.css +57 -392
  19. package/src/client/components/identity-manager/identity-manager.html +0 -114
  20. package/src/client/components/identity-manager/identity-manager.mts +356 -543
  21. package/src/client/components/keystone-creator/keystone-creator.mts +4 -3
  22. package/src/client/components/keystone-details/keystone-details.css +569 -0
  23. package/src/client/components/keystone-details/keystone-details.html +127 -0
  24. package/src/client/components/keystone-details/keystone-details.mts +1013 -0
  25. package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +33 -0
  26. package/src/client/components/keystone-scrubber/keystone-scrubber.css +46 -0
  27. package/src/client/components/keystone-scrubber/keystone-scrubber.html +15 -0
  28. package/src/client/components/keystone-scrubber/keystone-scrubber.mts +356 -0
  29. package/src/client/ui/shell/space-gib-shell-service.mts +4 -0
  30. package/src/server/path-constants.mts +12 -0
  31. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +33 -0
  32. package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +66 -0
  33. package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +140 -0
  34. package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +190 -0
  35. package/src/server/server.mts +6 -0
  36. package/dist/client/chunk-734MMI4C.mjs +0 -42
  37. package/dist/client/chunk-734MMI4C.mjs.map +0 -7
  38. package/dist/client/chunk-SMOZ2D5E.mjs +0 -2049
@@ -2,28 +2,35 @@ import styleCss from "../../style.css";
2
2
  import thisCss from "./identity-manager.css";
3
3
  import thisHtml from "./identity-manager.html";
4
4
 
5
- import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { delay, extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
6
  import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
7
7
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
8
- import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
8
+ import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
9
  import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
10
+ import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
11
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
- import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
12
+ import { getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
13
+ import { mut8Timeline } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
14
+ import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs";
12
15
  import {
13
- IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
16
+ IbGibDynamicComponentMetaBase,
17
+ IbGibDynamicComponentInstanceBase_ParentOfTabs,
14
18
  } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
15
19
  import {
16
- ElementsBase, IbGibDynamicComponentInstance,
20
+ ElementsBase, ChildInfoBase, IbGibDynamicComponentInstance,
17
21
  IbGibDynamicComponentInstanceInitOpts,
18
22
  } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
23
+ import { getDeterministicColorInfo, getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
19
24
  import { SettingsType } from "@ibgib/web-gib/dist/common/settings/settings-constants.mjs";
20
- import { Settings_General } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs";
25
+ import { Settings_General, Settings_IdentityManager } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs";
26
+ import { getSectionName } from "@ibgib/web-gib/dist/common/settings/settings-helpers.mjs";
21
27
  import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
22
28
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
23
29
 
24
30
  import { getComponentCtorArg, getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
25
31
  import { APP_CONFIG } from "../../constants.mjs";
26
32
  import { devLog } from "../../dev-tools.mjs";
33
+ import { KEYSTONE_ATOM } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
27
34
 
28
35
  export const IDENTITY_MANAGER_COMPONENT_NAME = 'ibgib-identity-manager';
29
36
 
@@ -62,6 +69,9 @@ export class IdentityManagerComponentMeta extends IbGibDynamicComponentMetaBase
62
69
  }
63
70
  }
64
71
 
72
+ interface IdentityTabInfo extends ChildInfoBase<IbGibDynamicComponentInstance<any, any>> {
73
+ }
74
+
65
75
  /**
66
76
  * References to DOM elements within the component's shadow root.
67
77
  */
@@ -72,82 +82,53 @@ interface IdentityManagerElements extends ElementsBase {
72
82
  btnAddIdentityEl: HTMLButtonElement;
73
83
  noIdentityPlaceholderEl: HTMLDivElement;
74
84
  btnCreatePlaceholderEl: HTMLButtonElement;
75
- identityDetailsViewEl: HTMLDivElement;
76
- identityAddrEl: HTMLElement;
77
- identityGenEl: HTMLElement;
78
- identityTimestampEl: HTMLElement;
79
- identityUuidEl: HTMLElement;
80
- identityNameEl: HTMLElement;
81
- identityDescriptionEl: HTMLElement;
82
- identityFrameDetailsEl: HTMLPreElement;
83
- identityAggrDetailsEl: HTMLPreElement;
84
-
85
- // Scrubber elements
86
- btnScrubFirstEl: HTMLButtonElement;
87
- btnScrubPrev10El: HTMLButtonElement;
88
- btnScrubPrevEl: HTMLButtonElement;
89
- scrubberStatusEl: HTMLSpanElement;
90
- btnScrubNextEl: HTMLButtonElement;
91
- btnScrubNext10El: HTMLButtonElement;
92
- btnScrubLatestEl: HTMLButtonElement;
93
-
94
- poolsContainerEl: HTMLDivElement;
95
- btnVerifyChainEl: HTMLButtonElement;
96
- // btnSyncIdentityEl: HTMLButtonElement;
97
- btnSetActiveEl: HTMLButtonElement;
98
- verificationStatusEl: HTMLDivElement;
99
- replicasListEl: HTMLDivElement;
100
- // inputReplicaUrlEl: HTMLInputElement;
101
- // btnAddReplicaEl: HTMLButtonElement;
102
- rawAccordionHeaderEl: HTMLDivElement;
103
- rawAccordionContentEl: HTMLDivElement;
104
- rawKeystoneJsonEl: HTMLPreElement;
105
- delegatesListEl: HTMLTableSectionElement;
106
85
  }
107
86
 
108
87
  /**
109
88
  * Concrete implementation of the Identity Manager component.
110
89
  */
111
90
  export class IdentityManagerComponentInstance
112
- extends IbGibDynamicComponentInstanceBase<IbGib_V1, IdentityManagerElements>
91
+ extends IbGibDynamicComponentInstanceBase_ParentOfTabs<Settings_IdentityManager, IbGib_V1, IdentityManagerElements, IdentityTabInfo>
113
92
  implements IbGibDynamicComponentInstance<IbGib_V1, IdentityManagerElements> {
114
93
 
115
- protected lc: string = `[IdentityManagerComponentInstance]`;
94
+ protected override lc: string = `[IdentityManagerComponentInstance]`;
116
95
 
117
- private activeAddr: string | null = null;
118
96
  private globalActiveAddr: string | null = null;
119
97
  private _onIdentityChanged: ((e: any) => void) | null = null;
120
- private history: any[] = [];
121
- private currentFrameIndex: number = -1;
122
- private lastLoadedHistoryAddr: string | null = null;
123
- private keystoneCache: Map<string, { username: string, description: string }> = new Map();
98
+ private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean, n: number }> = new Map();
99
+ metaspace: MetaspaceService | undefined;
100
+
101
+ protected get settingsType(): SettingsType {
102
+ return SettingsType.identityManager;
103
+ }
124
104
 
125
105
  constructor() {
126
106
  super();
127
107
  }
128
108
 
129
- private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string }> {
109
+ private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string, isPrimary: boolean, n: number }> {
130
110
  const cached = this.keystoneCache.get(addr);
131
111
  if (cached) return cached;
132
112
 
133
113
  const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
134
- if (!metaspace) return { username: '', description: '' };
114
+ if (!metaspace) return { username: '', description: '', isPrimary: false, n: 0 };
135
115
  const space = await metaspace.getLocalUserSpace({ lock: false });
136
- if (!space) return { username: '', description: '' };
116
+ if (!space) return { username: '', description: '', isPrimary: false, n: 0 };
137
117
 
138
118
  try {
139
- const res = await metaspace.get({ addr, space });
140
- if (res.success && res.ibGibs && res.ibGibs.length > 0) {
141
- const keystone = res.ibGibs[0] as KeystoneIbGib_V1;
142
- const keystoneSvc = new KeystoneService_V1();
143
- const aggregated = await keystoneSvc.getAggregateDetails({
144
- latestKeystone: keystone,
145
- metaspace,
146
- space
147
- });
148
- const username = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '';
149
- const description = aggregated?.description || aggregated?.profile?.description || '';
150
- const details = { username, description };
119
+ const keystoneSvc = new KeystoneService_V1();
120
+ const info = await keystoneSvc.getKeystoneCommonInfo({
121
+ addr,
122
+ metaspace,
123
+ space
124
+ });
125
+ if (info) {
126
+ const details = {
127
+ username: info.name,
128
+ description: info.description,
129
+ isPrimary: info.isPrimary,
130
+ n: info.n
131
+ };
151
132
  this.keystoneCache.set(addr, details);
152
133
  return details;
153
134
  }
@@ -155,10 +136,14 @@ export class IdentityManagerComponentInstance
155
136
  console.warn(`Error getting details for keystone ${addr}: ${extractErrorMsg(err)}`);
156
137
  }
157
138
 
158
- return { username: '', description: '' };
139
+ return { username: '', description: '', isPrimary: false, n: 0 };
159
140
  }
160
141
 
161
142
  override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
143
+ if (!this.metaspace) {
144
+ this.metaspace = await getGlobalMetaspace_waitIfNeeded({ delayIntervalMs: 50 });
145
+ }
146
+ opts.ibGibAddr = await this.metaspace.getLatestAddr({ addr: opts.ibGibAddr }) ?? opts.ibGibAddr;
162
147
  await super.initialize(opts);
163
148
  }
164
149
 
@@ -172,39 +157,8 @@ export class IdentityManagerComponentInstance
172
157
  this.elements.btnAddIdentityEl = shadow.getElementById('btn-add-identity') as HTMLButtonElement;
173
158
  this.elements.noIdentityPlaceholderEl = shadow.getElementById('no-identity-placeholder') as HTMLDivElement;
174
159
  this.elements.btnCreatePlaceholderEl = shadow.getElementById('btn-create-placeholder') as HTMLButtonElement;
175
- this.elements.identityDetailsViewEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
176
- this.elements.identityAddrEl = shadow.getElementById('identity-addr') as HTMLElement;
177
- this.elements.identityGenEl = shadow.getElementById('identity-gen') as HTMLSpanElement;
178
- this.elements.identityTimestampEl = shadow.getElementById('identity-timestamp') as HTMLSpanElement;
179
- this.elements.identityUuidEl = shadow.getElementById('identity-uuid') as HTMLSpanElement;
180
- this.elements.identityNameEl = shadow.getElementById('identity-name') as HTMLElement;
181
- this.elements.identityDescriptionEl = shadow.getElementById('identity-description') as HTMLElement;
182
- this.elements.identityFrameDetailsEl = shadow.getElementById('identity-frame-details') as HTMLPreElement;
183
- this.elements.identityAggrDetailsEl = shadow.getElementById('identity-aggr-details') as HTMLPreElement;
184
-
185
- // Scrubber elements
186
- this.elements.btnScrubFirstEl = shadow.getElementById('btn-scrub-first') as HTMLButtonElement;
187
- this.elements.btnScrubPrev10El = shadow.getElementById('btn-scrub-prev10') as HTMLButtonElement;
188
- this.elements.btnScrubPrevEl = shadow.getElementById('btn-scrub-prev') as HTMLButtonElement;
189
- this.elements.scrubberStatusEl = shadow.getElementById('scrubber-status') as HTMLSpanElement;
190
- this.elements.btnScrubNextEl = shadow.getElementById('btn-scrub-next') as HTMLButtonElement;
191
- this.elements.btnScrubNext10El = shadow.getElementById('btn-scrub-next10') as HTMLButtonElement;
192
- this.elements.btnScrubLatestEl = shadow.getElementById('btn-scrub-latest') as HTMLButtonElement;
193
-
194
- this.elements.poolsContainerEl = shadow.getElementById('pools-container') as HTMLDivElement;
195
- this.elements.btnVerifyChainEl = shadow.getElementById('btn-verify-chain') as HTMLButtonElement;
196
- // this.elements.btnSyncIdentityEl = shadow.getElementById('btn-sync-identity') as HTMLButtonElement;
197
- this.elements.btnSetActiveEl = shadow.getElementById('btn-set-active') as HTMLButtonElement;
198
- this.elements.verificationStatusEl = shadow.getElementById('verification-status') as HTMLDivElement;
199
- this.elements.replicasListEl = shadow.getElementById('replicas-list') as HTMLDivElement;
200
- // this.elements.inputReplicaUrlEl = shadow.getElementById('input-replica-url') as HTMLInputElement;
201
- // this.elements.btnAddReplicaEl = shadow.getElementById('btn-add-replica') as HTMLButtonElement;
202
- this.elements.rawAccordionHeaderEl = shadow.getElementById('raw-accordion-header') as HTMLDivElement;
203
- this.elements.rawAccordionContentEl = shadow.getElementById('raw-accordion-content') as HTMLDivElement;
204
- this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json') as HTMLPreElement;
205
- this.elements.delegatesListEl = shadow.getElementById('delegates-list') as HTMLTableSectionElement;
206
-
207
- // ElementBase requires contentEl (points to the main container/body area)
160
+
161
+ // ElementBase requires contentEl
208
162
  this.elements.contentEl = shadow.getElementById('identity-manager-content') as HTMLDivElement;
209
163
 
210
164
  this.initHandlers();
@@ -216,12 +170,65 @@ export class IdentityManagerComponentInstance
216
170
  };
217
171
  window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
218
172
 
173
+ // Listen for requests to view details of a delegate (from child details component)
174
+ this.addEventListener('ibgib-view-keystone-details', async (e: any) => {
175
+ const addr = e.detail?.addr;
176
+ if (addr) {
177
+ await this.activateIbGib({ addr });
178
+ }
179
+ });
180
+
219
181
  if (this.ibGibAddr === ROOT_ADDR || !this.ibGib) {
220
182
  await this.loadKeystonesIndex();
221
183
  } else {
222
184
  await this.initSettings();
223
185
  }
186
+
224
187
  await this.renderUI();
188
+
189
+ // Spin off tab restore or defaults to allow createdPromise to resolve first
190
+ this.restoreOrInitTabs();
191
+ }
192
+
193
+ private async restoreOrInitTabs(): Promise<void> {
194
+ const settings = await this.getSettings<Settings_IdentityManager>({
195
+ settingsType: this.settingsType,
196
+ useCase: 'current',
197
+ });
198
+ if (settings) {
199
+ const validAddrs = (settings.openChildTjpAddrs ?? []).filter(addr => {
200
+ if (!addr || addr === '^' || addr === 'gib') return false;
201
+ const { gib } = getIbAndGib({ ibGibAddr: addr });
202
+ return !!gib && gib !== 'gib' && gib !== '^';
203
+ });
204
+ if (validAddrs.length !== (settings.openChildTjpAddrs ?? []).length) {
205
+ settings.openChildTjpAddrs = validAddrs;
206
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
207
+ if (metaspace) {
208
+ const sectionName_current = await getSectionName({
209
+ settingsType: this.settingsType,
210
+ useCase: 'current',
211
+ });
212
+ await mut8Timeline({
213
+ timeline: this.settings!.ibGib!,
214
+ metaspace,
215
+ mut8Opts: {
216
+ dataToAddOrPatch: {
217
+ sections: {
218
+ [sectionName_current]: settings,
219
+ }
220
+ },
221
+ },
222
+ });
223
+ }
224
+ }
225
+ }
226
+
227
+ if (!settings || (settings.openChildTjpAddrs ?? []).length === 0) {
228
+ await this.loadDefaultPrimaryIdentityTabs();
229
+ } else {
230
+ await this.reopenOldTabs();
231
+ }
225
232
  }
226
233
 
227
234
  override async disconnected(): Promise<void> {
@@ -252,45 +259,20 @@ export class IdentityManagerComponentInstance
252
259
  }
253
260
  }
254
261
 
255
-
256
-
257
262
  private initHandlers() {
258
- // Setup raw JSON accordion toggle
259
- this.elements!.rawAccordionHeaderEl.addEventListener('click', () => this.toggleAccordion());
260
-
261
263
  // Buttons
262
264
  this.elements!.btnAddIdentityEl.addEventListener('click', () => this.handleAddNewIdentityFlow());
263
265
  this.elements!.btnCreatePlaceholderEl.addEventListener('click', () => this.handleAddNewIdentityFlow());
264
- this.elements!.btnVerifyChainEl.addEventListener('click', () => this.handleVerifyIdentityChain());
265
- // this.elements!.btnSyncIdentityEl.addEventListener('click', () => this.handleSyncIdentity());
266
- this.elements!.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
267
- // this.elements!.btnAddReplicaEl.addEventListener('click', () => this.handleAddReplicaSpace());
268
-
269
- // Scrubber Buttons
270
- this.elements!.btnScrubFirstEl.addEventListener('click', () => this.scrubFirst());
271
- this.elements!.btnScrubPrev10El.addEventListener('click', () => this.scrubPrev10());
272
- this.elements!.btnScrubPrevEl.addEventListener('click', () => this.scrubPrev());
273
- this.elements!.btnScrubNextEl.addEventListener('click', () => this.scrubNext());
274
- this.elements!.btnScrubNext10El.addEventListener('click', () => this.scrubNext10());
275
- this.elements!.btnScrubLatestEl.addEventListener('click', () => this.scrubLatest());
276
266
 
277
267
  // Convert vertical mouse wheel scrolling into horizontal scroll for tabs
278
268
  this.elements!.tabsListEl.addEventListener('wheel', (e: WheelEvent) => {
279
269
  if (e.deltaY !== 0) {
280
270
  e.preventDefault();
281
- // this.elements!.tabsListEl.scrollLeft += e.deltaY;
282
271
  this.elements!.tabsListEl.scrollBy({ left: e.deltaY, behavior: 'smooth' });
283
272
  }
284
273
  }, { passive: false });
285
274
  }
286
275
 
287
- private toggleAccordion() {
288
- const header = this.elements!.rawAccordionHeaderEl;
289
- const content = this.elements!.rawAccordionContentEl;
290
- header.classList.toggle('expanded');
291
- content.classList.toggle('expanded');
292
- }
293
-
294
276
  protected async handleAddNewIdentityFlow() {
295
277
  const lc = `${this.lc}[${this.handleAddNewIdentityFlow.name}]`;
296
278
  devLog(`${lc} Triggering identity creation/import flow...`);
@@ -302,14 +284,21 @@ export class IdentityManagerComponentInstance
302
284
  useRegExpPrefilter: true,
303
285
  });
304
286
  if (creator) {
305
- // Hide placeholder and details panel
287
+ // Hide placeholder
306
288
  this.elements!.noIdentityPlaceholderEl.classList.add('hidden');
307
- this.elements!.identityDetailsViewEl.classList.add('hidden');
308
289
 
309
- // Clear any previous creator
290
+ // Clear any previous creator/details
310
291
  const existing = this.elements!.contentEl.querySelector('ibgib-keystone-creator');
311
292
  if (existing) { existing.remove(); }
312
293
 
294
+ // Clear active child component inside ParentOfTabs to prevent conflict
295
+ const active = this.activeChildInfo;
296
+ if (active) {
297
+ active.childBtnEl.classList.remove('active');
298
+ active.active = false;
299
+ }
300
+
301
+ this.elements!.contentEl.innerHTML = '';
313
302
  this.elements!.contentEl.appendChild(creator as any);
314
303
  devLog(`${lc} Mounted ibgib-keystone-creator successfully.`);
315
304
  } else {
@@ -317,79 +306,13 @@ export class IdentityManagerComponentInstance
317
306
  }
318
307
  } catch (error) {
319
308
  console.error(`${lc} ${extractErrorMsg(error)}`);
320
- this.setVerificationStatus(`Failed to load creator: ${extractErrorMsg(error)}`, "error");
321
- }
322
- }
323
-
324
- protected async handleVerifyIdentityChain() {
325
- const lc = `${this.lc}[${this.handleVerifyIdentityChain.name}]`;
326
- devLog(`${lc} Verifying identity keystone timeline...`);
327
- this.setVerificationStatus("Verification request sent...", "info");
328
- try {
329
- // Skeleton logic placeholder
330
- this.setVerificationStatus("Verification complete. Root Keystone is verified (Genesis phase 1).", "success");
331
- } catch (error) {
332
- this.setVerificationStatus(`Verification failed: ${extractErrorMsg(error)}`, "error");
333
309
  }
334
310
  }
335
311
 
336
- // leave this in
337
- // protected async handleSyncIdentity() {
338
- // const lc = `${this.lc}[${this.handleSyncIdentity.name}]`;
339
- // devLog(`${lc} Starting Peer-to-Peer sync saga...`);
340
- // this.setVerificationStatus("Starting synchronization session...", "info");
341
- // }
342
-
343
- // leave this in
344
- // protected async handleAddReplicaSpace() {
345
- // const lc = `${this.lc}[${this.handleAddReplicaSpace.name}]`;
346
- // const url = this.elements!.inputReplicaUrlEl.value;
347
- // if (!url) {
348
- // this.setVerificationStatus("Please enter a valid space replication URL.", "error");
349
- // return;
350
- // }
351
- // devLog(`${lc} Registering replica space: ${url}`);
352
-
353
- // // Add to UI list for verification
354
- // const replicaItem = document.createElement('div');
355
- // replicaItem.className = 'address-code';
356
- // replicaItem.style.marginTop = '0.5rem';
357
- // replicaItem.textContent = url;
358
- // this.elements!.replicasListEl.appendChild(replicaItem);
359
-
360
- // this.elements!.inputReplicaUrlEl.value = '';
361
- // this.setVerificationStatus(`Registered replica endpoint: ${url}`, "success");
362
- // }
363
-
364
- /**
365
- * dispatches the event to set {@link activeAddr} (if truthy) as the active
366
- * identity. IOW, this "logs it in".
367
- */
368
- protected async handleSetActiveIdentity() {
369
- if (!this.activeAddr) { return; }
370
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
371
- detail: { activeIdentityAddr: this.activeAddr },
372
- bubbles: true,
373
- composed: true
374
- }));
375
- }
376
-
377
- private setVerificationStatus(msg: string, type: 'info' | 'success' | 'error') {
378
- const statusEl = this.elements!.verificationStatusEl;
379
- statusEl.textContent = msg;
380
- statusEl.className = `status-msg ${type}`;
381
- }
382
-
383
- private async viewIdentityDetails(addr: string): Promise<void> {
384
- this.activeAddr = addr;
385
- await this.renderUI();
386
- }
387
-
388
312
  private async resolveActiveIdentity() {
389
313
  const lc = `${this.lc}[resolveActiveIdentity]`;
390
314
  try {
391
315
  if (!this.settings) {
392
- this.activeAddr = null;
393
316
  this.globalActiveAddr = null;
394
317
  return;
395
318
  }
@@ -400,17 +323,22 @@ export class IdentityManagerComponentInstance
400
323
  const activeAddrInSettings = (generalSettings as any)?.activeIdentityAddr;
401
324
 
402
325
  const registeredKeystones = this.ibGib?.rel8ns?.keystone ?? [];
403
- if (activeAddrInSettings && registeredKeystones.includes(activeAddrInSettings)) {
326
+ const detailsPromises = registeredKeystones.map(async addr => {
327
+ const details = await this.getKeystoneDetails(addr);
328
+ return { addr, isPrimary: details?.isPrimary ?? false };
329
+ });
330
+ const resolvedDetails = await Promise.all(detailsPromises);
331
+ const primaryKeystones = resolvedDetails
332
+ .filter(item => item.isPrimary)
333
+ .map(item => item.addr);
334
+
335
+ if (activeAddrInSettings && primaryKeystones.includes(activeAddrInSettings)) {
404
336
  this.globalActiveAddr = activeAddrInSettings;
405
- } else if (registeredKeystones.length > 0) {
406
- this.globalActiveAddr = registeredKeystones[0];
337
+ } else if (primaryKeystones.length > 0) {
338
+ this.globalActiveAddr = primaryKeystones[0];
407
339
  } else {
408
340
  this.globalActiveAddr = null;
409
341
  }
410
-
411
- if (!this.activeAddr) {
412
- this.activeAddr = this.globalActiveAddr;
413
- }
414
342
  } catch (error) {
415
343
  console.error(`${lc} ${extractErrorMsg(error)}`);
416
344
  this.globalActiveAddr = null;
@@ -421,406 +349,291 @@ export class IdentityManagerComponentInstance
421
349
  const lc = `${this.lc}[${this.renderUI.name}]`;
422
350
  if (!this.elements) { return; }
423
351
 
424
- // always resolve the current active identity, even if we aren't
425
- // necessarily rendering the details for that identity (if it isn't
426
- // this.activeAddr)
427
352
  await this.resolveActiveIdentity();
428
353
 
429
354
  const keystones = this.ibGib?.rel8ns?.keystone ?? [];
430
-
431
- if (keystones.length === 0) {
432
- this.elements.tabsListEl.innerHTML = '';
355
+ if (keystones.length === 0 && this.childInfos.length === 0) {
433
356
  this.elements.noIdentityPlaceholderEl.classList.remove('hidden');
434
- this.elements.identityDetailsViewEl.classList.add('hidden');
435
-
436
- const existing = this.elements.contentEl.querySelector('ibgib-keystone-creator');
437
- if (existing && !this.elements.noIdentityPlaceholderEl.classList.contains('hidden')) {
438
- existing.remove();
439
- }
440
- return; /* <<<< returns early */
357
+ this.elements.contentEl.innerHTML = '';
358
+ return;
441
359
  }
442
360
 
443
361
  this.elements.noIdentityPlaceholderEl.classList.add('hidden');
444
362
 
445
- // Render Tabs
446
- this.elements.tabsListEl.innerHTML = '';
447
- for (const addr of keystones) {
448
- const tabEl = document.createElement('button');
449
- tabEl.className = `tab-btn ${addr === this.activeAddr ? 'active' : ''} ${addr === this.globalActiveAddr ? 'global-active' : ''}`;
450
-
451
- // Initial fallback label and tooltip
452
- let displayTab = addr;
453
- if (displayTab.length > 12) {
454
- displayTab = displayTab.substring(0, 5) + '...' + displayTab.substring(displayTab.length - 4);
455
- }
456
- tabEl.textContent = displayTab;
457
- tabEl.title = addr;
363
+ // Render tab highlights
364
+ for (const info of this.childInfos) {
365
+ const btn = info.childBtnEl;
366
+ btn.className = `tab-btn ${info.active ? 'active' : ''} ${info.addr === this.globalActiveAddr ? 'global-active' : ''}`;
367
+ }
368
+ }
458
369
 
459
- // Fetch details and update tab text/tooltip
370
+ private async loadDefaultPrimaryIdentityTabs() {
371
+ const keystones = (this.ibGib?.rel8ns?.keystone ?? []).filter(addr => {
372
+ if (!addr || addr === '^' || addr === 'gib') return false;
373
+ const { gib } = getIbAndGib({ ibGibAddr: addr });
374
+ return !!gib && gib !== 'gib' && gib !== '^';
375
+ });
376
+ const detailsPromises = keystones.map(async addr => {
460
377
  const details = await this.getKeystoneDetails(addr);
461
- let name = details.username;
462
- const maxTabLen = 16;
463
- if (name) {
464
- if (name.length > maxTabLen) {
465
- name = name.substring(0, maxTabLen - 3) + '...';
466
- }
467
- tabEl.textContent = name;
468
- }
469
-
470
- const tooltipParts: string[] = [];
471
- if (details.username) {
472
- tooltipParts.push(`User: ${details.username}`);
473
- }
474
- if (details.description) {
475
- const desc = details.description.length > 100
476
- ? details.description.substring(0, 97) + '...'
477
- : details.description;
478
- tooltipParts.push(`Desc: ${desc}`);
479
- }
480
- tooltipParts.push(`Addr: ${addr}`);
481
- tabEl.title = tooltipParts.join('\n');
378
+ return { addr, isPrimary: details?.isPrimary ?? false };
379
+ });
380
+ const resolvedDetails = await Promise.all(detailsPromises);
381
+ const primaryKeystones = resolvedDetails
382
+ .filter(item => item.isPrimary)
383
+ .map(item => item.addr);
482
384
 
483
- tabEl.addEventListener('click', async () => {
484
- await this.viewIdentityDetails(addr);
485
- });
486
- this.elements.tabsListEl.appendChild(tabEl);
385
+ if (primaryKeystones.length === 0) {
386
+ this.elements!.noIdentityPlaceholderEl.classList.remove('hidden');
387
+ return;
487
388
  }
488
389
 
489
- if (this.activeAddr) {
490
- const existing = this.elements.contentEl.querySelector('ibgib-keystone-creator');
491
- if (existing) { existing.remove(); }
492
-
493
- this.elements.identityDetailsViewEl.classList.remove('hidden');
390
+ this.elements!.noIdentityPlaceholderEl.classList.add('hidden');
494
391
 
495
- // Update Set Active button state
496
- if (this.activeAddr === this.globalActiveAddr) {
497
- this.elements.btnSetActiveEl.disabled = true;
498
- this.elements.btnSetActiveEl.textContent = "Active Identity";
499
- this.elements.btnSetActiveEl.classList.add('active-primary');
500
- } else {
501
- this.elements.btnSetActiveEl.disabled = false;
502
- this.elements.btnSetActiveEl.textContent = "Set as Active Identity";
503
- this.elements.btnSetActiveEl.classList.remove('active-primary');
504
- }
505
-
506
- if (this.activeAddr !== this.lastLoadedHistoryAddr) {
507
- await this.loadHistory(this.activeAddr);
508
- this.lastLoadedHistoryAddr = this.activeAddr;
392
+ this.reloadingTabs = true;
393
+ try {
394
+ for (const addr of primaryKeystones) {
395
+ await this.activateIbGib({ addr });
509
396
  }
510
-
511
- await this.updateFrameDetailsView();
512
- } else {
513
- this.elements.identityDetailsViewEl.classList.add('hidden');
514
- this.clearActiveDetails();
397
+ } finally {
398
+ this.reloadingTabs = false;
515
399
  }
516
- }
517
400
 
518
- private clearActiveDetails() {
519
- if (!this.elements) return;
520
- this.elements.identityGenEl.textContent = '-';
521
- this.elements.identityTimestampEl.textContent = '-';
522
- this.elements.identityUuidEl.textContent = '-';
523
- this.elements.identityNameEl.textContent = '-';
524
- this.elements.identityDescriptionEl.textContent = '-';
525
- this.elements.identityFrameDetailsEl.textContent = '{}';
526
- this.elements.identityAggrDetailsEl.textContent = '{}';
527
- this.elements.scrubberStatusEl.textContent = 'Frame - / -';
528
- this.elements.poolsContainerEl.innerHTML = '';
529
- if (this.elements.delegatesListEl) {
530
- this.elements.delegatesListEl.innerHTML = '';
531
- }
532
- this.history = [];
533
- this.currentFrameIndex = -1;
401
+ // Activate the global active identity (if open), or the first primary one
402
+ const activeToOpen = primaryKeystones.includes(this.globalActiveAddr || '')
403
+ ? this.globalActiveAddr!
404
+ : primaryKeystones[0];
405
+ await this.activateIbGib({ addr: activeToOpen });
534
406
  }
535
407
 
536
- private async loadHistory(tipAddr: string): Promise<void> {
537
- const lc = `${this.lc}[loadHistory]`;
538
- this.history = [];
539
- this.currentFrameIndex = -1;
540
-
541
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
542
- if (!metaspace) return;
543
- const space = await metaspace.getLocalUserSpace({ lock: false });
544
- if (!space) return;
545
-
546
- let currentAddr: string | undefined = tipAddr;
547
- const tempHistory: KeystoneIbGib_V1[] = [];
548
-
549
- while (currentAddr) {
550
- const res = await metaspace.get({ addr: currentAddr, space });
551
- if (!res.success || !res.ibGibs || res.ibGibs.length === 0) {
552
- console.warn(`${lc} Could not find frame at ${currentAddr}`);
553
- break;
554
- }
555
- const keystone = res.ibGibs[0] as KeystoneIbGib_V1;
556
- tempHistory.push(keystone);
557
-
558
- if (keystone.data?.n === 0) {
559
- break;
560
- }
561
-
562
- const past = keystone.rel8ns?.past;
563
- currentAddr = (past && past.length > 0) ? past[past.length - 1] : undefined;
564
- }
565
-
566
- this.history = tempHistory.reverse();
567
- this.currentFrameIndex = this.history.length - 1;
568
- }
408
+ public override async activateIbGib({ addr, ibGib }: { addr?: IbGibAddr; ibGib?: IbGib_V1; }): Promise<void> {
409
+ const lc = `${this.lc}[activateIbGib]`;
410
+ try {
411
+ if (!this.metaspace) { throw new Error(`(UNEXPECTED) this.metaspace falsy? (E: f9e0e9a1427899eba8e469c18d7ae826)`); }
569
412
 
570
- private scrubTo(index: number) {
571
- if (this.history.length === 0) return;
572
- this.currentFrameIndex = Math.max(0, Math.min(index, this.history.length - 1));
573
- this.updateFrameDetailsView();
574
- }
413
+ await super.activateIbGib({ addr, ibGib });
575
414
 
576
- private scrubFirst() {
577
- this.scrubTo(0);
578
- }
579
415
 
580
- private scrubPrev10() {
581
- this.scrubTo(this.currentFrameIndex - 10);
582
- }
416
+ if (!this.activeChildInfo) { throw new Error(`(UNEXPECTED) activeChildInfo falsy after activateIbGib? (E: d76bd8bc300f3fdc486d50c8b2644826)`); }
583
417
 
584
- private scrubPrev() {
585
- this.scrubTo(this.currentFrameIndex - 1);
586
- }
418
+ ibGib ??= this.activeChildInfo.component?.ibGib;
587
419
 
588
- private scrubNext() {
589
- this.scrubTo(this.currentFrameIndex + 1);
590
- }
420
+ if (!ibGib) { throw new Error(`(UNEXPECTED) ibGib falsy? (E: 5b6ec745dd32fd2128635728e3f35e26)`); }
591
421
 
592
- private scrubNext10() {
593
- this.scrubTo(this.currentFrameIndex + 10);
594
- }
422
+ if (!ibGib.ib.startsWith(KEYSTONE_ATOM)) {
423
+ throw new Error(`ibGib.ib (${ibGib.ib}) does not start with keystone atom (${KEYSTONE_ATOM}) (E: e0df410381c8e569eb6da996e79b6826)`);
424
+ }
595
425
 
596
- private scrubLatest() {
597
- this.scrubTo(this.history.length - 1);
598
- }
426
+ this.elements!.noIdentityPlaceholderEl.classList.add('hidden');
599
427
 
600
- private async updateFrameDetailsView(): Promise<void> {
601
- const lc = `${this.lc}[updateFrameDetailsView]`;
602
- if (!this.elements) return;
428
+ if (!this.reloadingTabs) {
429
+ const settings_current = await this.getSettings<Settings_IdentityManager>({
430
+ settingsType: this.settingsType,
431
+ useCase: 'current',
432
+ });
433
+ if (settings_current) {
434
+ let modified = false;
435
+ const tjpAddr = getTjpAddr({ ibGib: this.activeChildInfo.component?.ibGib || ibGib!, defaultIfNone: 'incomingAddr' });
436
+ if (tjpAddr) {
437
+ if (!settings_current.openChildTjpAddrs.includes(tjpAddr)) {
438
+ settings_current.openChildTjpAddrs.push(tjpAddr);
439
+ modified = true;
440
+ }
441
+ if (settings_current.activeChildTjpAddr !== tjpAddr) {
442
+ settings_current.activeChildTjpAddr = tjpAddr;
443
+ modified = true;
444
+ }
445
+ }
446
+ if (modified) {
447
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
448
+ if (metaspace) {
449
+ const sectionName_current = await getSectionName({
450
+ settingsType: this.settingsType,
451
+ useCase: 'current',
452
+ });
453
+ await mut8Timeline({
454
+ timeline: this.settings!.ibGib!,
455
+ metaspace,
456
+ mut8Opts: {
457
+ dataToAddOrPatch: {
458
+ sections: {
459
+ [sectionName_current]: settings_current,
460
+ }
461
+ },
462
+ },
463
+ });
464
+ }
465
+ }
466
+ }
467
+ }
603
468
 
604
- if (this.history.length === 0 || this.currentFrameIndex < 0 || this.currentFrameIndex >= this.history.length) {
605
- this.clearActiveDetails();
606
- return;
469
+ await this.renderUI();
470
+ } catch (error) {
471
+ console.error(`${lc} ${extractErrorMsg(error)}`);
607
472
  }
473
+ }
608
474
 
609
- const keystone = this.history[this.currentFrameIndex];
610
- const keystoneAddr = getIbGibAddr({ ibGib: keystone });
611
-
612
- // Update Verifiable Address
613
- this.elements.identityAddrEl.textContent = keystoneAddr;
614
-
615
- // Update Generation
616
- this.elements.identityGenEl.textContent = keystone.data?.n !== undefined ? String(keystone.data.n) : '0 (Genesis)';
617
-
618
- // Update Timestamp
619
- const timestampStr = keystone.data?.timestamp;
620
- let formattedTimestamp = 'N/A';
621
- if (timestampStr) {
622
- const parsed = parseInt(timestampStr);
623
- if (!isNaN(parsed)) {
624
- formattedTimestamp = new Date(parsed).toLocaleString();
625
- } else {
626
- formattedTimestamp = new Date(timestampStr).toLocaleString();
475
+ protected async getLoadedChildInfo({
476
+ addr,
477
+ ibGib,
478
+ }: {
479
+ addr: IbGibAddr,
480
+ ibGib?: IbGib_V1,
481
+ }): Promise<IdentityTabInfo> {
482
+ const lc = `${this.lc}[getLoadedChildInfo]`;
483
+ try {
484
+ if (!addr) { throw new Error(`(UNEXPECTED) addr falsy?`); }
485
+ if (addr === '^' || addr === 'gib' || getIbAndGib({ ibGibAddr: addr }).gib === '^') {
486
+ throw new Error(`Cannot load child info for primitive address ${addr}`);
487
+ }
488
+ const tjpGib = getGibInfo({ ibGibAddr: addr }).tjpGib ?? getIbAndGib({ ibGibAddr: addr }).gib;
489
+
490
+ const fnCreateAndLoadChildComponent = async () => {
491
+ const componentSvc = await getComponentSvc();
492
+ const detailsComponent = await componentSvc.getComponentInstance({
493
+ path: 'ibgib-keystone-scrubber',
494
+ ibGibAddr: addr,
495
+ useRegExpPrefilter: true,
496
+ });
497
+ if (!detailsComponent) {
498
+ throw new Error(`(UNEXPECTED) detailsComponent falsy?`);
499
+ }
500
+ return detailsComponent;
627
501
  }
628
- }
629
- this.elements.identityTimestampEl.textContent = formattedTimestamp;
630
-
631
- // Update UUID
632
- this.elements.identityUuidEl.textContent = keystone.data?.uuid || 'N/A';
633
-
634
- // Update Scrubber Status
635
- this.elements.scrubberStatusEl.textContent = `Frame ${this.currentFrameIndex + 1} / ${this.history.length}`;
636
-
637
- // Disable/enable scrubber buttons based on boundaries
638
- this.elements.btnScrubFirstEl.disabled = this.currentFrameIndex === 0;
639
- this.elements.btnScrubPrev10El.disabled = this.currentFrameIndex === 0;
640
- this.elements.btnScrubPrevEl.disabled = this.currentFrameIndex === 0;
641
- this.elements.btnScrubNextEl.disabled = this.currentFrameIndex === this.history.length - 1;
642
- this.elements.btnScrubNext10El.disabled = this.currentFrameIndex === this.history.length - 1;
643
- this.elements.btnScrubLatestEl.disabled = this.currentFrameIndex === this.history.length - 1;
644
-
645
- // Show Frame Details
646
- const frameDetails = keystone.data?.frameDetails;
647
- this.elements.identityFrameDetailsEl.textContent = frameDetails ? pretty(frameDetails) : '{}';
648
502
 
649
- // Get and Show Aggregated details up to the scrolled-to frame
650
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
651
- if (metaspace) {
652
- const space = await metaspace.getLocalUserSpace({ lock: false });
653
- if (space) {
654
- try {
655
- const keystoneSvc = new KeystoneService_V1();
656
- const aggregated = await keystoneSvc.getAggregateDetails({
657
- latestKeystone: keystone,
658
- metaspace,
659
- space
660
- });
503
+ let tabInfo: IdentityTabInfo;
661
504
 
662
- this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
505
+ const filtered = this.childInfos.filter(x => {
506
+ const tabGibInfo = getGibInfo({ ibGibAddr: x.addr });
507
+ const tabTjpGib = tabGibInfo.tjpGib ?? getIbAndGib({ ibGibAddr: x.addr }).gib;
508
+ return tabTjpGib === tjpGib;
509
+ });
663
510
 
664
- // Update Username and Description fields
665
- this.elements.identityNameEl.textContent = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '-';
666
- this.elements.identityDescriptionEl.textContent = aggregated?.description || aggregated?.profile?.description || '-';
667
- } catch (err) {
668
- console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
669
- this.elements.identityAggrDetailsEl.textContent = '{}';
670
- this.elements.identityNameEl.textContent = '-';
671
- this.elements.identityDescriptionEl.textContent = '-';
511
+ if (filtered.length > 0) {
512
+ if (filtered.length !== 1) { throw new Error(`(UNEXPECTED) filtered.length !== 1?`); }
513
+ tabInfo = filtered[0];
514
+ if (!tabInfo.component) {
515
+ tabInfo.component = await fnCreateAndLoadChildComponent();
672
516
  }
673
517
  } else {
674
- this.elements.identityAggrDetailsEl.textContent = '{}';
675
- this.elements.identityNameEl.textContent = '-';
676
- this.elements.identityDescriptionEl.textContent = '-';
518
+ const detailsComponent = await fnCreateAndLoadChildComponent();
519
+ let i = 0;
520
+ while (!detailsComponent.ibGib!) {
521
+ await delay(100);
522
+ i++;
523
+ if (i > 50) { // 5 seconds
524
+ debugger; // error loading details component ibgib
525
+ throw new Error(`(UNEXPECTED) detailsComponent.ibGib falsy? didn't load? (E: de50689834124914f770520f1eccb826)`);
526
+ }
527
+ }
528
+ ibGib = detailsComponent.ibGib!;
529
+ addr = getIbGibAddr({ ibGib });
530
+ const tabBtnEl = await this.addChild({ addr, ibGib });
531
+ tabInfo = {
532
+ addr,
533
+ childBtnEl: tabBtnEl,
534
+ component: detailsComponent,
535
+ active: false,
536
+ };
537
+ this.childInfos.push(tabInfo);
677
538
  }
678
- } else {
679
- this.elements.identityAggrDetailsEl.textContent = '{}';
680
- this.elements.identityNameEl.textContent = '-';
681
- this.elements.identityDescriptionEl.textContent = '-';
539
+
540
+ return tabInfo;
541
+ } catch (error) {
542
+ console.error(`${lc} ${extractErrorMsg(error)}`);
543
+ throw error;
682
544
  }
545
+ }
683
546
 
684
- // Render Raw Timeline JSON
685
- this.elements.rawKeystoneJsonEl.textContent = pretty(keystone);
547
+ protected override async addChild({
548
+ addr,
549
+ ibGib,
550
+ }: {
551
+ ibGib: IbGib_V1,
552
+ addr?: IbGibAddr,
553
+ }): Promise<HTMLElement> {
554
+ const lc = `${this.lc}[addChild]`;
555
+ try {
556
+ addr ??= getIbGibAddr({ ibGib });
557
+ if (addr === '^' || addr === 'gib' || getIbAndGib({ ibGibAddr: addr }).gib === '^') {
558
+ throw new Error(`Cannot add child tab for primitive address ${addr}`);
559
+ }
560
+ if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy?`); }
561
+ const { tabsListEl } = this.elements;
686
562
 
687
- // Render Challenge Pools
688
- this.renderChallengePools(keystone.data?.challengePools || []);
563
+ addr ??= getIbGibAddr({ ibGib });
689
564
 
690
- // Render Delegates
691
- await this.renderDelegates(keystone.data?.delegates);
692
- }
565
+ const childBtnEl = document.createElement('button');
566
+ childBtnEl.className = 'tab-btn';
567
+ childBtnEl.id = `identity-tab-button-${addr}`;
693
568
 
694
- private renderChallengePools(pools: any[]) {
695
- if (!this.elements) return;
696
- const container = this.elements.poolsContainerEl;
697
- container.innerHTML = '';
569
+ await this.updateTabTitleAndText({ span: childBtnEl, ibGib, addr });
698
570
 
699
- if (!pools || pools.length === 0) {
700
- container.innerHTML = `<p class="description">No challenge pools found in this keystone.</p>`;
701
- return;
702
- }
571
+ const details = await this.getKeystoneDetails(addr);
572
+ if (!details.isPrimary) {
573
+ const closeBtnEl = document.createElement('span');
574
+ closeBtnEl.className = 'close-tab-button';
575
+ closeBtnEl.textContent = ' ×';
576
+ closeBtnEl.style.marginLeft = '0.5rem';
577
+ closeBtnEl.style.cursor = 'pointer';
578
+ childBtnEl.appendChild(closeBtnEl);
579
+ closeBtnEl.addEventListener('click', async (event) => {
580
+ event.stopPropagation(); // Prevent activating the tab
581
+ await this.closeTab({ ibGib });
582
+ });
583
+ }
703
584
 
704
- for (const pool of pools) {
705
- const card = document.createElement('div');
706
- card.className = 'pool-card';
707
-
708
- const config = pool.config || {};
709
- const behavior = config.behavior || {};
710
- const allowedVerbs = config.allowedVerbs || [];
711
- const activeCount = pool.challenges ? Object.keys(pool.challenges).length : 0;
712
- const isForeign = !!pool.isForeign;
713
-
714
- // Generate HTML for the challenges list
715
- const challenges = pool.challenges || {};
716
- const challengeListHtml = Object.entries(challenges).map(([id, challenge]: [string, any]) => {
717
- const truncatedHash = challenge.hash && challenge.hash.length > 20
718
- ? challenge.hash.substring(0, 10) + '...' + challenge.hash.substring(challenge.hash.length - 8)
719
- : challenge.hash || 'N/A';
720
- return `
721
- <div class="pool-challenge-item">
722
- <span class="challenge-id">${id}</span>
723
- <span class="challenge-hash" title="${challenge.hash || ''}">${truncatedHash}</span>
724
- </div>
725
- `;
726
- }).join('');
727
-
728
- card.innerHTML = `
729
- <div class="pool-header">
730
- <span class="pool-title">${pool.id || 'Unnamed Pool'}</span>
731
- <span class="pool-tag ${isForeign ? 'foreign' : 'native'}">${isForeign ? 'Foreign' : 'Native'}</span>
732
- </div>
733
- <div class="pool-grid">
734
- <div class="pool-field">
735
- <label>Challenge Type</label>
736
- <span>${config.type || 'N/A'}</span>
737
- </div>
738
- <div class="pool-field">
739
- <label>Target Size</label>
740
- <span>${behavior.size ?? 'N/A'}</span>
741
- </div>
742
- <div class="pool-field">
743
- <label>Active Challenges</label>
744
- <span>${activeCount}</span>
745
- </div>
746
- <div class="pool-field">
747
- <label>Replenish Strategy</label>
748
- <span>${behavior.replenish || 'N/A'}</span>
749
- </div>
750
- <div class="pool-field">
751
- <label>Allowed Verbs</label>
752
- <span>${allowedVerbs.length > 0 ? allowedVerbs.join(', ') : 'Any'}</span>
753
- </div>
754
- <div class="pool-field">
755
- <label>FIFO / Random cost</label>
756
- <span>FIFO: ${behavior.selectSequentially ?? 0}, Rand: ${behavior.selectRandomly ?? 0}</span>
757
- </div>
758
- </div>
759
-
760
- <!-- Collapsible Challenges List -->
761
- <details class="pool-details">
762
- <summary class="pool-details-summary">Challenges (${activeCount})</summary>
763
- <div class="pool-challenges-list">
764
- ${challengeListHtml || '<p class="placeholder-text" style="padding: 0.5rem; text-align: center;">No challenges active.</p>'}
765
- </div>
766
- </details>
767
- `;
768
- container.appendChild(card);
585
+ tabsListEl.appendChild(childBtnEl);
586
+ childBtnEl.addEventListener('click', async (event) => {
587
+ await this.activateIbGib({ addr });
588
+ });
589
+
590
+ return childBtnEl;
591
+ } catch (error) {
592
+ console.error(`${lc} ${extractErrorMsg(error)}`);
593
+ throw error;
769
594
  }
770
595
  }
771
596
 
772
- private async renderDelegates(delegates: Record<string, any> | undefined) {
773
- if (!this.elements) return;
774
- const listEl = this.elements.delegatesListEl;
775
- listEl.innerHTML = '';
776
-
777
- if (!delegates || Object.keys(delegates).length === 0) {
778
- listEl.innerHTML = `
779
- <tr>
780
- <td colspan="3" style="text-align: center; color: var(--clr-text-secondary, #b3b3b3);">
781
- No registered delegates.
782
- </td>
783
- </tr>
784
- `;
785
- return;
597
+ private async updateTabTitleAndText({
598
+ span,
599
+ ibGib,
600
+ addr,
601
+ }: {
602
+ span: HTMLElement,
603
+ ibGib: IbGib_V1,
604
+ addr: string,
605
+ }): Promise<void> {
606
+ const details = await this.getKeystoneDetails(addr);
607
+ let name = details.username;
608
+ const maxTabLen = 16;
609
+ if (name) {
610
+ if (name.length > maxTabLen) {
611
+ name = name.substring(0, maxTabLen - 3) + '...';
612
+ }
613
+ span.textContent = name;
614
+ } else {
615
+ let displayTab = addr;
616
+ if (displayTab.length > 12) {
617
+ displayTab = displayTab.substring(0, 5) + '...' + displayTab.substring(displayTab.length - 4);
618
+ }
619
+ span.textContent = displayTab;
786
620
  }
787
621
 
788
- for (const [delegateTjpAddr, info] of Object.entries(delegates)) {
789
- const tr = document.createElement('tr');
790
-
791
- const tdName = document.createElement('td');
792
- tdName.textContent = 'Loading...';
793
-
794
- const tdTjp = document.createElement('td');
795
- tdTjp.textContent = delegateTjpAddr;
796
- tdTjp.title = delegateTjpAddr;
797
-
798
- const tdVerbs = document.createElement('td');
799
- tdVerbs.textContent = info.allowedVerbs ? info.allowedVerbs.join(', ') : '-';
800
- tdVerbs.title = tdVerbs.textContent;
801
-
802
- tr.appendChild(tdName);
803
- tr.appendChild(tdTjp);
804
- tr.appendChild(tdVerbs);
805
- listEl.appendChild(tr);
806
-
807
- if (info.delegateAddr) {
808
- this.getKeystoneDetails(info.delegateAddr).then(details => {
809
- const textParts: string[] = [];
810
- if (details.username) textParts.push(details.username);
811
- if (details.description) textParts.push(details.description);
812
- tdName.textContent = textParts.join(' / ') || 'Unnamed Delegate';
813
- tdName.title = `Addr: ${info.delegateAddr}\n` + textParts.join('\n');
814
- }).catch(err => {
815
- tdName.textContent = 'Unknown Delegate';
816
- });
817
- } else {
818
- tdName.textContent = 'No Address';
819
- }
622
+ const tooltipParts: string[] = [];
623
+ if (details.username) {
624
+ tooltipParts.push(`User: ${details.username}`);
820
625
  }
626
+ if (details.description) {
627
+ const desc = details.description.length > 100
628
+ ? details.description.substring(0, 97) + '...'
629
+ : details.description;
630
+ tooltipParts.push(`Desc: ${desc}`);
631
+ }
632
+ tooltipParts.push(`Addr: ${addr}`);
633
+ span.title = tooltipParts.join('\n');
821
634
  }
822
635
 
823
- override async handleContextUpdated(): Promise<void> {
824
- await this.renderUI();
636
+ protected async removeTabBtn({ tabInfo }: { tabInfo: IdentityTabInfo }): Promise<void> {
637
+ this.elements!.tabsListEl.removeChild(tabInfo.childBtnEl);
825
638
  }
826
639
  }