@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibgib/space-gib",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "description": "ibgib storage and provider service — SaaS at ibgib.space",
6
6
  "scripts": {
@@ -12,11 +12,11 @@
12
12
  "build:test": "tsc -p tsconfig.test.json"
13
13
  },
14
14
  "dependencies": {
15
- "@ibgib/core-gib": "^0.1.62",
15
+ "@ibgib/core-gib": "^0.1.64",
16
16
  "@ibgib/helper-gib": "^0.0.36",
17
- "@ibgib/ts-gib": "^0.5.32",
18
- "@ibgib/web-gib": "^0.0.48",
19
- "@ibgib/node-gib": "^0.0.6"
17
+ "@ibgib/ts-gib": "^0.5.33",
18
+ "@ibgib/web-gib": "^0.0.50",
19
+ "@ibgib/node-gib": "^0.0.8"
20
20
  },
21
21
  "engines": {
22
22
  "node": ">=20.0.0"
@@ -0,0 +1,45 @@
1
+ script.mjs:7 space-gib early script executing...
2
+ chunk-OVS3VYAL.mjs:27047 [getSpaceGibShellSvc] initializing SpaceGibShellService singleton on globalThis... (I: genuuid)
3
+ chunk-OVS3VYAL.mjs:108 [initialize] does nothing atow (I: 65bb46c88c82b637ad928b8bf1a07225)
4
+ index.mjs:24 [spinOffStartup]: 0.134033203125 ms DOMContentLoaded fired
5
+ index.mjs:26 [spinOffStartup]: 57.553955078125 ms initIbGibStorage complete
6
+ bootstrap.mjs:9643 [space-gib bootstrap] version: 0.0.6
7
+ bootstrap.mjs:7586 [bootstrapIbGibApp] starting...
8
+ bootstrap.mjs:366 [buildArgInfos] starting... (I: f389aaa490796dfd823bc7b9d4e58b23)
9
+ bootstrap.mjs:376 identifier: interactive (I: 519ce129a1e34753a9070d3a205c9a8b)
10
+ bootstrap.mjs:462 [buildArgInfos] complete.
11
+ bootstrap.mjs:7639 [execFromArgs] starting...
12
+ bootstrap.mjs:7681 [initMetaspace] starting...
13
+ chunk-D2MM5RDN.mjs:72 [Violation] 'setTimeout' handler took 70ms
14
+ bootstrap.mjs:2495 [Metaspace_Webspace][initialize] initialized (I: e3b3a9652c09445cb055013166ff089c)
15
+ bootstrap.mjs:7708 [initMetaspace] complete.
16
+ chunk-OVS3VYAL.mjs:20874 [registerFunctionInfos] starting... (I: 14fee8e27384a040494542182031ae25)
17
+ chunk-OVS3VYAL.mjs:20893 [registerFunctionInfos] complete.
18
+ bootstrap.mjs:7725 [execInteractive] starting...
19
+ bootstrap.mjs:7758 [defaultGetOrCreateApp] starting...
20
+ bootstrap.mjs:7826 [defaultGetOrCreateApp] complete.
21
+ bootstrap.mjs:8503 [SpaceGibApp_V1] space-gib initialized and ready.
22
+ bootstrap.mjs:7748 [execInteractive] complete.
23
+ bootstrap.mjs:7671
24
+ [execFromArgs] complete.
25
+ chunk-OVS3VYAL.mjs:27020 [SpaceGibShellService][onEngineReady] SpaceGib Shell Service is ready.
26
+ bootstrap.mjs:7629
27
+ [bootstrapIbGibApp] complete.
28
+ index.mjs:28 [spinOffStartup]: 920.193115234375 ms dynamicallyLoadBootstrapScript complete
29
+ index.mjs:30 [spinOffStartup]: 924.22314453125 ms initDevTools complete
30
+ index.mjs:31 [spinOffStartup]: 924.443115234375 ms
31
+ chunk-OVS3VYAL.mjs:26572 yes this is being updated! I am just wondering if I am seeing old code in the browser, i.e., if refresh is happening or just getting cache (I: 6599db11c1086062b8f5e86db89cba26)
32
+ chunk-OVS3VYAL.mjs:26575 KeystoneScrubberComponentInstance[ctor] (I: 3adca837d5c898d8e839d23c59538126)
33
+ chunk-OVS3VYAL.mjs:26583 [KeystoneScrubberComponentInstance][initialize] starting... (I: 75eecfc01af7f3fa9897932ec5457526)
34
+ chunk-OVS3VYAL.mjs:26638 [KeystoneScrubberComponentInstance][loadIbGib] starting... (I: a4a9181bbb58a22cc71c8745a91ec426)
35
+ chunk-OVS3VYAL.mjs:26648 [KeystoneScrubberComponentInstance][loadIbGib] complete.
36
+ chunk-OVS3VYAL.mjs:26592 [KeystoneScrubberComponentInstance][initialize] complete.
37
+ chunk-OVS3VYAL.mjs:26600 [KeystoneScrubberComponentInstance][created] starting... (I: bc16e6212928658ed8034c75b6279526)
38
+ chunk-OVS3VYAL.mjs:26572 yes this is being updated! I am just wondering if I am seeing old code in the browser, i.e., if refresh is happening or just getting cache (I: 6599db11c1086062b8f5e86db89cba26)
39
+ chunk-OVS3VYAL.mjs:26575 KeystoneScrubberComponentInstance[ctor] (I: 3adca837d5c898d8e839d23c59538126)
40
+ chunk-OVS3VYAL.mjs:26583 [KeystoneScrubberComponentInstance][initialize] starting... (I: 75eecfc01af7f3fa9897932ec5457526)
41
+ chunk-OVS3VYAL.mjs:26638 [KeystoneScrubberComponentInstance][loadIbGib] starting... (I: a4a9181bbb58a22cc71c8745a91ec426)
42
+ chunk-OVS3VYAL.mjs:26648 [KeystoneScrubberComponentInstance][loadIbGib] complete.
43
+ chunk-OVS3VYAL.mjs:26592 [KeystoneScrubberComponentInstance][initialize] complete.
44
+ chunk-OVS3VYAL.mjs:26600 [KeystoneScrubberComponentInstance][created] starting... (I: bc16e6212928658ed8034c75b6279526)
45
+ chunk-OVS3VYAL.mjs:24584 [IdentityManagerComponentInstance][activateIbGib] same tab already active. returning early. childInfo.addr: keystone^442D637B4066E3D0A73A467C14F71FE9FDAA5C5A7FC7EA28064877A63A914C44.5D0EC2067890F88736364BBE9BD59C80DCB99FC85132B3637072D274077D8CA6 (user just clicked on the same tab) (I: e67c0d02f3143c49c7a20c82d0730225)
@@ -8,4 +8,4 @@
8
8
  /**
9
9
  * Version of space-gib, auto-updated by the build process.
10
10
  */
11
- export const AUTO_GENERATED_VERSION = '0.0.5';
11
+ export const AUTO_GENERATED_VERSION = '0.0.7';
@@ -23,6 +23,15 @@ import { registerDeprecatedFunctionNamesAndFunctionInfos_Web } from "./api/funct
23
23
 
24
24
  import { getSpaceGibShellSvc } from "./ui/shell/space-gib-shell-service.mjs";
25
25
 
26
+ // Early check: if we are in an OAuth callback popup redirect, notify parent window and close immediately.
27
+ const urlParams = new URLSearchParams(window.location.search);
28
+ if (urlParams.has('code') && urlParams.has('state') && window.opener) {
29
+ const code = urlParams.get('code');
30
+ const state = urlParams.get('state');
31
+ window.opener.postMessage({ type: 'sso-oauth-callback', code, state }, window.location.origin);
32
+ window.close();
33
+ }
34
+
26
35
  console.log(`[space-gib bootstrap] version: ${AUTO_GENERATED_VERSION}`);
27
36
 
28
37
  // ---------------------------------------------------------------------------
@@ -18,14 +18,13 @@
18
18
  background: var(--tab-background-color-active, rgba(255, 255, 255, 0.1));
19
19
  border: 1px solid var(--tab-border-color-active, rgba(80, 250, 123, 0.3));
20
20
  border-radius: var(--radius-lg, 20px);
21
- cursor: pointer;
21
+ cursor: default;
22
22
  transition: all 0.2s ease-in-out;
23
23
  }
24
24
 
25
25
  .identity-pill:hover {
26
26
  background: rgba(80, 250, 123, 0.15);
27
27
  border-color: var(--clr-accent, #50fa7b);
28
- transform: translateY(-1px);
29
28
  box-shadow: 0 2px 8px rgba(80, 250, 123, 0.1);
30
29
  }
31
30
 
@@ -72,3 +71,42 @@
72
71
  .hidden {
73
72
  display: none !important;
74
73
  }
74
+
75
+ /* Styled selector dropdown */
76
+ .identity-select {
77
+ font-family: var(--font-sans, sans-serif);
78
+ font-size: 0.85rem;
79
+ color: var(--clr-text-primary, #ffffff);
80
+ background: transparent;
81
+ border: none;
82
+ cursor: pointer;
83
+ font-weight: 500;
84
+ outline: none;
85
+ padding: 0 0.2rem;
86
+ max-width: 150px;
87
+ text-overflow: ellipsis;
88
+ }
89
+
90
+ .identity-select option {
91
+ background: var(--clr-bg-surface, #1e1e2e);
92
+ color: var(--clr-text-primary, #ffffff);
93
+ }
94
+
95
+ /* Gear manage button */
96
+ .manage-btn {
97
+ background: transparent;
98
+ border: none;
99
+ font-size: 1rem;
100
+ cursor: pointer;
101
+ padding: 0 0.2rem;
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ color: var(--clr-text-secondary, #b3b3b3);
106
+ transition: color 0.2s ease, transform 0.2s ease;
107
+ }
108
+
109
+ .manage-btn:hover {
110
+ color: var(--clr-accent, #50fa7b);
111
+ transform: rotate(45deg);
112
+ }
@@ -1,8 +1,9 @@
1
1
  <div id="container" class="header-widget-container">
2
2
  <!-- Active Identity Pill -->
3
- <div id="active-identity-pill" class="identity-pill hidden" title="Active Domain Identity. Click to manage.">
3
+ <div id="active-identity-pill" class="identity-pill hidden">
4
4
  <div id="identity-avatar" class="avatar-circle">-</div>
5
- <span id="identity-address-truncated" class="address-text">No Identity</span>
5
+ <select id="identity-selector" class="identity-select" title="Switch active identity"></select>
6
+ <button id="btn-manage-identities" class="manage-btn" title="Manage Identities">⚙️</button>
6
7
  </div>
7
8
 
8
9
  <!-- Fallback Action Button -->
@@ -68,7 +68,8 @@ export class IdentityHeaderComponentMeta extends IbGibDynamicComponentMetaBase {
68
68
  interface IdentityHeaderElements extends ElementsBase {
69
69
  containerEl: HTMLDivElement;
70
70
  activeIdentityPillEl: HTMLDivElement;
71
- identityAddressTruncatedEl: HTMLSpanElement;
71
+ identitySelectorEl: HTMLSelectElement;
72
+ btnManageIdentitiesEl: HTMLButtonElement;
72
73
  btnCreateIdentityEl: HTMLButtonElement;
73
74
  identityAvatarEl: HTMLDivElement;
74
75
  }
@@ -83,34 +84,34 @@ export class IdentityHeaderComponentInstance
83
84
  protected lc: string = `[IdentityHeaderComponentInstance]`;
84
85
  private activeAddr: string | null = null;
85
86
  private _onRequestIdentityChange: ((e: any) => void) | null = null;
86
- private keystoneCache: Map<string, { username: string, description: string }> = new Map();
87
+ private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean }> = new Map();
87
88
 
88
89
  constructor() {
89
90
  super();
90
91
  }
91
92
 
92
- private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string }> {
93
+ private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string, isPrimary: boolean }> {
93
94
  const cached = this.keystoneCache.get(addr);
94
95
  if (cached) return cached;
95
96
 
96
97
  const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
97
- if (!metaspace) return { username: '', description: '' };
98
+ if (!metaspace) return { username: '', description: '', isPrimary: false };
98
99
  const space = await metaspace.getLocalUserSpace({ lock: false });
99
- if (!space) return { username: '', description: '' };
100
+ if (!space) return { username: '', description: '', isPrimary: false };
100
101
 
101
102
  try {
102
- const res = await metaspace.get({ addr, space });
103
- if (res.success && res.ibGibs && res.ibGibs.length > 0) {
104
- const keystone = res.ibGibs[0] as KeystoneIbGib_V1;
105
- const keystoneSvc = new KeystoneService_V1();
106
- const aggregated = await keystoneSvc.getAggregateDetails({
107
- latestKeystone: keystone,
108
- metaspace,
109
- space
110
- });
111
- const username = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '';
112
- const description = aggregated?.description || aggregated?.profile?.description || '';
113
- const details = { username, description };
103
+ const keystoneSvc = new KeystoneService_V1();
104
+ const info = await keystoneSvc.getKeystoneCommonInfo({
105
+ addr,
106
+ metaspace,
107
+ space
108
+ });
109
+ if (info) {
110
+ const details = {
111
+ username: info.name,
112
+ description: info.description,
113
+ isPrimary: info.isPrimary
114
+ };
114
115
  this.keystoneCache.set(addr, details);
115
116
  return details;
116
117
  }
@@ -118,7 +119,7 @@ export class IdentityHeaderComponentInstance
118
119
  console.warn(`Error getting details for keystone ${addr}: ${extractErrorMsg(err)}`);
119
120
  }
120
121
 
121
- return { username: '', description: '' };
122
+ return { username: '', description: '', isPrimary: false };
122
123
  }
123
124
 
124
125
 
@@ -132,7 +133,8 @@ export class IdentityHeaderComponentInstance
132
133
  const shadow = this.shadowRoot!;
133
134
  this.elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
134
135
  this.elements.activeIdentityPillEl = shadow.getElementById('active-identity-pill') as HTMLDivElement;
135
- this.elements.identityAddressTruncatedEl = shadow.getElementById('identity-address-truncated') as HTMLSpanElement;
136
+ this.elements.identitySelectorEl = shadow.getElementById('identity-selector') as HTMLSelectElement;
137
+ this.elements.btnManageIdentitiesEl = shadow.getElementById('btn-manage-identities') as HTMLButtonElement;
136
138
  this.elements.btnCreateIdentityEl = shadow.getElementById('btn-create-identity') as HTMLButtonElement;
137
139
  this.elements.identityAvatarEl = shadow.getElementById('identity-avatar') as HTMLDivElement;
138
140
 
@@ -182,9 +184,24 @@ export class IdentityHeaderComponentInstance
182
184
  }
183
185
 
184
186
  private initHandlers() {
185
- // Handle clicks: route to the full identity manager
186
- this.elements!.activeIdentityPillEl.addEventListener('click', () => this.navigateToManager());
187
187
  this.elements!.btnCreateIdentityEl.addEventListener('click', () => this.navigateToManager());
188
+ this.elements!.btnManageIdentitiesEl.addEventListener('click', (e) => {
189
+ e.stopPropagation();
190
+ this.navigateToManager();
191
+ });
192
+ this.elements!.identitySelectorEl.addEventListener('click', (e) => {
193
+ e.stopPropagation();
194
+ });
195
+ this.elements!.identitySelectorEl.addEventListener('change', () => {
196
+ const selectedAddr = this.elements!.identitySelectorEl.value;
197
+ if (selectedAddr) {
198
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
199
+ detail: { activeIdentityAddr: selectedAddr },
200
+ bubbles: true,
201
+ composed: true
202
+ }));
203
+ }
204
+ });
188
205
  }
189
206
 
190
207
  private navigateToManager() {
@@ -213,16 +230,25 @@ export class IdentityHeaderComponentInstance
213
230
 
214
231
  // Verify it is actually one of the registered keystones in the index relations
215
232
  const registeredKeystones = this.ibGib?.rel8ns?.keystone ?? [];
216
- if (activeAddrInSettings && registeredKeystones.includes(activeAddrInSettings)) {
233
+ const detailsPromises = registeredKeystones.map(async addr => {
234
+ const details = await this.getKeystoneDetails(addr);
235
+ return { addr, isPrimary: details?.isPrimary ?? false };
236
+ });
237
+ const resolvedDetails = await Promise.all(detailsPromises);
238
+ const primaryKeystones = resolvedDetails
239
+ .filter(item => item.isPrimary)
240
+ .map(item => item.addr);
241
+
242
+ if (activeAddrInSettings && primaryKeystones.includes(activeAddrInSettings)) {
217
243
  this.activeAddr = activeAddrInSettings;
218
- } else if (registeredKeystones.length > 0) {
219
- this.activeAddr = registeredKeystones[0];
244
+ } else if (primaryKeystones.length > 0) {
245
+ this.activeAddr = primaryKeystones[0];
220
246
  } else {
221
247
  this.activeAddr = null;
222
248
  }
223
249
 
224
250
  // Sync with globalState.identity
225
- const globalState = getIbGibGlobalThis_SpaceGib(APP_CONFIG) as any;
251
+ const globalState = getIbGibGlobalThis_SpaceGib(APP_CONFIG);
226
252
  if (this.activeAddr) {
227
253
  const metaspace = globalState.metaspace;
228
254
  if (metaspace) {
@@ -230,12 +256,44 @@ export class IdentityHeaderComponentInstance
230
256
  if (space) {
231
257
  const res = await metaspace.get({ addr: this.activeAddr, space });
232
258
  if (res.success && res.ibGibs && res.ibGibs.length > 0) {
233
- globalState.identity = res.ibGibs[0];
259
+ const domainIdentity = res.ibGibs[0] as KeystoneIbGib_V1;
260
+
261
+ // Find delegate with the sync allowed verb
262
+ let syncDelegateIdentity: KeystoneIbGib_V1 | undefined;
263
+ const delegates = domainIdentity.data?.delegates;
264
+ if (delegates) {
265
+ for (const delegateInfo of Object.values(delegates)) {
266
+ if (delegateInfo.allowedVerbs?.includes('sync^gib') || (!delegateInfo.allowedVerbs && delegateInfo.delegateTjpAddr)) {
267
+ // Fetch the delegate's latest tip from space
268
+ const delegateLatestAddr = await metaspace.getLatestAddr({
269
+ addr: delegateInfo.delegateTjpAddr,
270
+ space
271
+ });
272
+ if (delegateLatestAddr) {
273
+ const resDelegate = await metaspace.get({ addr: delegateLatestAddr, space });
274
+ if (resDelegate.success && resDelegate.ibGibs && resDelegate.ibGibs.length > 0) {
275
+ syncDelegateIdentity = resDelegate.ibGibs[0] as KeystoneIbGib_V1;
276
+ break;
277
+ }
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ globalState.identity = {
284
+ resolved: true,
285
+ authenticated: true,
286
+ domainIdentity,
287
+ syncDelegateIdentity
288
+ };
234
289
  }
235
290
  }
236
291
  }
237
292
  } else {
238
- globalState.identity = undefined;
293
+ globalState.identity = {
294
+ resolved: false,
295
+ authenticated: false
296
+ };
239
297
  }
240
298
  } catch (error) {
241
299
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -246,29 +304,60 @@ export class IdentityHeaderComponentInstance
246
304
  }
247
305
 
248
306
  protected override async renderUI(): Promise<void> {
249
- if (!this.elements) return;
307
+ if (!this.elements) { return; }
250
308
 
251
309
  // Double check state on render
252
310
  await this.resolveActiveIdentity();
253
311
 
254
- if (this.activeAddr) {
312
+ const keystones = this.ibGib?.rel8ns?.keystone ?? [];
313
+ const detailsPromises = keystones.map(async addr => {
314
+ const details = await this.getKeystoneDetails(addr);
315
+ return { addr, isPrimary: details?.isPrimary ?? false };
316
+ });
317
+ const resolvedDetails = await Promise.all(detailsPromises);
318
+ const primaryKeystones = resolvedDetails
319
+ .filter(item => item.isPrimary)
320
+ .map(item => item.addr);
321
+
322
+ if (this.activeAddr && primaryKeystones.length > 0) {
255
323
  this.elements.activeIdentityPillEl.classList.remove('hidden');
256
324
  this.elements.btnCreateIdentityEl.classList.add('hidden');
257
325
 
258
326
  const addr = this.activeAddr;
259
327
 
260
- // Initial fallback render
261
- let displayAddr = addr;
262
- if (displayAddr.length > 14) {
263
- displayAddr = displayAddr.substring(0, 7) + '...' + displayAddr.substring(displayAddr.length - 5);
328
+ // Populate the dropdown selector
329
+ this.elements.identitySelectorEl.innerHTML = '';
330
+ for (const kAddr of primaryKeystones) {
331
+ const option = document.createElement('option');
332
+ option.value = kAddr;
333
+
334
+ let displayAddr = kAddr;
335
+ if (displayAddr.length > 14) {
336
+ displayAddr = displayAddr.substring(0, 7) + '...' + displayAddr.substring(displayAddr.length - 5);
337
+ }
338
+ option.textContent = displayAddr;
339
+
340
+ // Load details asynchronously so we don't block
341
+ this.getKeystoneDetails(kAddr).then(details => {
342
+ if (details.username) {
343
+ let name = details.username;
344
+ if (name.length > 14) {
345
+ name = name.substring(0, 11) + '...';
346
+ }
347
+ option.textContent = name;
348
+ }
349
+ }).catch(err => { });
350
+
351
+ this.elements.identitySelectorEl.appendChild(option);
264
352
  }
265
- this.elements.identityAddressTruncatedEl.textContent = displayAddr;
353
+ this.elements.identitySelectorEl.value = addr;
354
+
355
+ // Render active avatar
266
356
  this.elements.identityAvatarEl.textContent = '-';
267
357
  this.elements.identityAvatarEl.style.backgroundColor = 'var(--clr-accent)';
268
358
  this.elements.identityAvatarEl.style.color = '#11111b';
269
- this.elements.activeIdentityPillEl.title = `Active Identity: ${addr}\nClick to manage.`;
270
359
 
271
- // Load details dynamically
360
+ // Load details dynamically for the active identity
272
361
  const details = await this.getKeystoneDetails(addr);
273
362
 
274
363
  // First letter of username, or fallback to first char of address (or 'A')
@@ -290,16 +379,6 @@ export class IdentityHeaderComponentInstance
290
379
  this.elements.identityAvatarEl.style.backgroundColor = bg;
291
380
  this.elements.identityAvatarEl.style.color = fg;
292
381
 
293
- // Set user display name or address in pill
294
- let displayName = details.username;
295
- const maxLen = 14;
296
- if (displayName) {
297
- if (displayName.length > maxLen) {
298
- displayName = displayName.substring(0, maxLen - 3) + '...';
299
- }
300
- this.elements.identityAddressTruncatedEl.textContent = displayName;
301
- }
302
-
303
382
  // Title with full details
304
383
  const titleParts: string[] = [];
305
384
  if (details.username) {
@@ -312,7 +391,6 @@ export class IdentityHeaderComponentInstance
312
391
  titleParts.push(`Desc: ${desc}`);
313
392
  }
314
393
  titleParts.push(`Addr: ${addr}`);
315
- titleParts.push(`Click to manage.`);
316
394
  this.elements.activeIdentityPillEl.title = titleParts.join('\n');
317
395
  } else {
318
396
  this.elements.activeIdentityPillEl.classList.add('hidden');
@@ -323,10 +401,10 @@ export class IdentityHeaderComponentInstance
323
401
  private async handleRequestIdentityChange(e: any) {
324
402
  const lc = `${this.lc}[handleRequestIdentityChange]`;
325
403
  const addr = e.detail?.activeIdentityAddr;
326
- if (!addr) return;
404
+ if (!addr) { return; }
327
405
  devLog(`${lc} Request to change active identity to: ${addr}`);
328
406
  try {
329
- if (!this.settings) return;
407
+ if (!this.settings) { return; }
330
408
  const generalSettings = await this.getSettings<Settings_General>({
331
409
  settingsType: SettingsType.general,
332
410
  useCase: 'current',