@ibgib/space-gib 0.0.5 → 0.0.7

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