@ibgib/space-gib 0.0.4 → 0.0.6

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 (42) hide show
  1. package/README.md +7 -0
  2. package/dist/client/bootstrap.mjs +1 -1
  3. package/dist/client/chunk-734MMI4C.mjs +42 -0
  4. package/dist/client/chunk-734MMI4C.mjs.map +7 -0
  5. package/dist/client/{chunk-ZUEU37Z5.mjs → chunk-SMOZ2D5E.mjs} +170 -41
  6. package/dist/client/chunk-SMOZ2D5E.mjs.map +7 -0
  7. package/dist/client/index.html +35 -14
  8. package/dist/client/index.mjs +1 -1
  9. package/dist/client/script.mjs +1 -1
  10. package/dist/client/style.css +29 -5
  11. package/dist/server/server.mjs +338 -31
  12. package/dist/server/server.mjs.map +3 -3
  13. package/package.json +4 -4
  14. package/src/client/AUTO-GENERATED-version.mts +1 -1
  15. package/src/client/components/identity-header/identity-header.css +40 -2
  16. package/src/client/components/identity-header/identity-header.html +3 -2
  17. package/src/client/components/identity-header/identity-header.mts +87 -26
  18. package/src/client/components/identity-manager/IMPLEMENTATION.md +10 -10
  19. package/src/client/components/identity-manager/identity-manager.css +39 -0
  20. package/src/client/components/identity-manager/identity-manager.html +20 -0
  21. package/src/client/components/identity-manager/identity-manager.mts +59 -0
  22. package/src/client/components/keystone-creator/keystone-creator.mts +88 -12
  23. package/src/client/dev-tools/common.mts +505 -10
  24. package/src/client/dev-tools/phase-4-1.mts +19 -4
  25. package/src/client/dev-tools/phase-4-10.mts +77 -305
  26. package/src/client/dev-tools/phase-4-2.mts +46 -212
  27. package/src/client/dev-tools/phase-4-3.mts +57 -198
  28. package/src/client/dev-tools/phase-4-4.mts +57 -170
  29. package/src/client/dev-tools/phase-4-5.mts +48 -204
  30. package/src/client/dev-tools/phase-4-6.mts +37 -103
  31. package/src/client/dev-tools/phase-4-7.mts +44 -228
  32. package/src/client/dev-tools/phase-4-8.mts +33 -136
  33. package/src/client/dev-tools/phase-4-9.mts +33 -137
  34. package/src/client/dev-tools/phase-4.mts +30 -6
  35. package/src/client/dev-tools.mts +18 -0
  36. package/src/client/index.html +35 -14
  37. package/src/client/style.css +29 -5
  38. package/src/client/types.mts +4 -1
  39. package/dist/client/chunk-NCXKCVYS.mjs +0 -42
  40. package/dist/client/chunk-NCXKCVYS.mjs.map +0 -7
  41. package/dist/client/chunk-ZUEU37Z5.mjs.map +0 -7
  42. 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.4",
3
+ "version": "0.0.6",
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.61",
15
+ "@ibgib/core-gib": "^0.1.63",
16
16
  "@ibgib/helper-gib": "^0.0.36",
17
17
  "@ibgib/ts-gib": "^0.5.32",
18
- "@ibgib/web-gib": "^0.0.47",
19
- "@ibgib/node-gib": "^0.0.5"
18
+ "@ibgib/web-gib": "^0.0.49",
19
+ "@ibgib/node-gib": "^0.0.7"
20
20
  },
21
21
  "engines": {
22
22
  "node": ">=20.0.0"
@@ -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.4';
11
+ export const AUTO_GENERATED_VERSION = '0.0.6';
@@ -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
  }
@@ -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() {
@@ -222,7 +239,7 @@ export class IdentityHeaderComponentInstance
222
239
  }
223
240
 
224
241
  // Sync with globalState.identity
225
- const globalState = getIbGibGlobalThis_SpaceGib(APP_CONFIG) as any;
242
+ const globalState = getIbGibGlobalThis_SpaceGib(APP_CONFIG);
226
243
  if (this.activeAddr) {
227
244
  const metaspace = globalState.metaspace;
228
245
  if (metaspace) {
@@ -230,12 +247,44 @@ export class IdentityHeaderComponentInstance
230
247
  if (space) {
231
248
  const res = await metaspace.get({ addr: this.activeAddr, space });
232
249
  if (res.success && res.ibGibs && res.ibGibs.length > 0) {
233
- globalState.identity = res.ibGibs[0];
250
+ const domainIdentity = res.ibGibs[0] as KeystoneIbGib_V1;
251
+
252
+ // Find delegate with the sync allowed verb
253
+ let syncDelegateIdentity: KeystoneIbGib_V1 | undefined;
254
+ const delegates = domainIdentity.data?.delegates;
255
+ if (delegates) {
256
+ for (const delegateInfo of Object.values(delegates)) {
257
+ if (delegateInfo.allowedVerbs?.includes('sync^gib') || (!delegateInfo.allowedVerbs && delegateInfo.delegateTjpAddr)) {
258
+ // Fetch the delegate's latest tip from space
259
+ const delegateLatestAddr = await metaspace.getLatestAddr({
260
+ addr: delegateInfo.delegateTjpAddr,
261
+ space
262
+ });
263
+ if (delegateLatestAddr) {
264
+ const resDelegate = await metaspace.get({ addr: delegateLatestAddr, space });
265
+ if (resDelegate.success && resDelegate.ibGibs && resDelegate.ibGibs.length > 0) {
266
+ syncDelegateIdentity = resDelegate.ibGibs[0] as KeystoneIbGib_V1;
267
+ break;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ globalState.identity = {
275
+ resolved: true,
276
+ authenticated: true,
277
+ domainIdentity,
278
+ syncDelegateIdentity
279
+ };
234
280
  }
235
281
  }
236
282
  }
237
283
  } else {
238
- globalState.identity = undefined;
284
+ globalState.identity = {
285
+ resolved: false,
286
+ authenticated: false
287
+ };
239
288
  }
240
289
  } catch (error) {
241
290
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -251,24 +300,47 @@ export class IdentityHeaderComponentInstance
251
300
  // Double check state on render
252
301
  await this.resolveActiveIdentity();
253
302
 
254
- if (this.activeAddr) {
303
+ const keystones = this.ibGib?.rel8ns?.keystone ?? [];
304
+
305
+ if (this.activeAddr && keystones.length > 0) {
255
306
  this.elements.activeIdentityPillEl.classList.remove('hidden');
256
307
  this.elements.btnCreateIdentityEl.classList.add('hidden');
257
308
 
258
309
  const addr = this.activeAddr;
259
310
 
260
- // Initial fallback render
261
- let displayAddr = addr;
262
- if (displayAddr.length > 14) {
263
- displayAddr = displayAddr.substring(0, 7) + '...' + displayAddr.substring(displayAddr.length - 5);
311
+ // Populate the dropdown selector
312
+ this.elements.identitySelectorEl.innerHTML = '';
313
+ for (const kAddr of keystones) {
314
+ const option = document.createElement('option');
315
+ option.value = kAddr;
316
+
317
+ let displayAddr = kAddr;
318
+ if (displayAddr.length > 14) {
319
+ displayAddr = displayAddr.substring(0, 7) + '...' + displayAddr.substring(displayAddr.length - 5);
320
+ }
321
+ option.textContent = displayAddr;
322
+
323
+ // Load details asynchronously so we don't block
324
+ this.getKeystoneDetails(kAddr).then(details => {
325
+ if (details.username) {
326
+ let name = details.username;
327
+ if (name.length > 14) {
328
+ name = name.substring(0, 11) + '...';
329
+ }
330
+ option.textContent = name;
331
+ }
332
+ }).catch(err => {});
333
+
334
+ this.elements.identitySelectorEl.appendChild(option);
264
335
  }
265
- this.elements.identityAddressTruncatedEl.textContent = displayAddr;
336
+ this.elements.identitySelectorEl.value = addr;
337
+
338
+ // Render active avatar
266
339
  this.elements.identityAvatarEl.textContent = '-';
267
340
  this.elements.identityAvatarEl.style.backgroundColor = 'var(--clr-accent)';
268
341
  this.elements.identityAvatarEl.style.color = '#11111b';
269
- this.elements.activeIdentityPillEl.title = `Active Identity: ${addr}\nClick to manage.`;
270
342
 
271
- // Load details dynamically
343
+ // Load details dynamically for the active identity
272
344
  const details = await this.getKeystoneDetails(addr);
273
345
 
274
346
  // First letter of username, or fallback to first char of address (or 'A')
@@ -290,16 +362,6 @@ export class IdentityHeaderComponentInstance
290
362
  this.elements.identityAvatarEl.style.backgroundColor = bg;
291
363
  this.elements.identityAvatarEl.style.color = fg;
292
364
 
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
365
  // Title with full details
304
366
  const titleParts: string[] = [];
305
367
  if (details.username) {
@@ -312,7 +374,6 @@ export class IdentityHeaderComponentInstance
312
374
  titleParts.push(`Desc: ${desc}`);
313
375
  }
314
376
  titleParts.push(`Addr: ${addr}`);
315
- titleParts.push(`Click to manage.`);
316
377
  this.elements.activeIdentityPillEl.title = titleParts.join('\n');
317
378
  } else {
318
379
  this.elements.activeIdentityPillEl.classList.add('hidden');
@@ -68,9 +68,9 @@ Following the custom `web-gib` Web Component microframework, the component consi
68
68
  - [x] Document the custom `web-gib` Web Component microframework (covering element lifecycles, routing, parent-child delegation, and settings/tabs state persistence).
69
69
 
70
70
  ### Phase 5: UI/UX Enhancements & Bugs
71
- - [ ] Center/align the `<ibgib-identity-manager>` component instead of having it shoved to the left (maxing out at ~66% width of the center-panel).
71
+ - [x] Center/align the `<ibgib-identity-manager>` component instead of having it shoved to the left (maxing out at ~66% width of the center-panel).
72
72
  - [x] Adjust scrolling behavior so the entire manager or manager-content div scrolls, instead of limiting the scroll region to the accordion detail pre-tag.
73
- - [ ] Automatically select/activate the newly created identity keystone in the tab list upon creation.
73
+ - [x] Automatically select/activate the newly created identity keystone in the tab list upon creation.
74
74
 
75
75
  ### Phase 6: Detail Views (Pools & Keystone Metadata)
76
76
  - [x] Add a details view for challenge pools on the keystone (`KeystoneData_V1.challengePools`).
@@ -86,14 +86,14 @@ Following the custom `web-gib` Web Component microframework, the component consi
86
86
  - [x] Implement history scrubber navigation (First, Back 10, Back 1, Next 1, Next 10, Latest) to traverse the keystone timeline.
87
87
 
88
88
  ### Phase 8: Active Identity Management
89
- - [ ] Define reusable event constants (`EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` and `EVENT_IBGIB_IDENTITY_CHANGED`) in `libs/web-gib/src/ui/ui-constants.mts`.
90
- - [ ] Add checkbox in `<ibgib-keystone-creator>` to make the newly created identity the active one. Enforce/lock to checked if no identities exist yet.
91
- - [ ] If checked on creation, `<ibgib-keystone-creator>` dispatches `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` containing the new keystone address.
92
- - [ ] Add "Set as Active Identity" button to `<ibgib-identity-manager>` details view. When clicked, it dispatches `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` with the selected keystone address.
93
- - [ ] Decouple tab clicks in `<ibgib-identity-manager>` so they only browse details via `viewIdentityDetails(addr)` without setting active identity.
94
- - [ ] Update `<ibgib-identity-header>` to listen for `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE`. On receipt, write the address to its settings timeline, update `ibGibGlobalThis.identity`, re-render, and dispatch `EVENT_IBGIB_IDENTITY_CHANGED`.
95
- - [ ] Set `ibGibGlobalThis.identity` on `<ibgib-identity-header>` initialization using settings.
96
- - [ ] Update `<ibgib-identity-manager>` to subscribe to `EVENT_IBGIB_IDENTITY_CHANGED` and re-render/update its "Set as Active" button state accordingly.
89
+ - [x] Define reusable event constants (`EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` and `EVENT_IBGIB_IDENTITY_CHANGED`) in `libs/web-gib/src/ui/ui-constants.mts`.
90
+ - [x] Add checkbox in `<ibgib-keystone-creator>` to make the newly created identity the active one. Enforce/lock to checked if no identities exist yet.
91
+ - [x] If checked on creation, `<ibgib-keystone-creator>` dispatches `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` containing the new keystone address.
92
+ - [x] Add "Set as Active Identity" button to `<ibgib-identity-manager>` details view. When clicked, it dispatches `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE` with the selected keystone address.
93
+ - [x] Decouple tab clicks in `<ibgib-identity-manager>` so they only browse details via `viewIdentityDetails(addr)` without setting active identity.
94
+ - [x] Update `<ibgib-identity-header>` to listen for `EVENT_IBGIB_IDENTITY_REQUEST_CHANGE`. On receipt, write the address to its settings timeline, update `ibGibGlobalThis.identity`, re-render, and dispatch `EVENT_IBGIB_IDENTITY_CHANGED`.
95
+ - [x] Set `ibGibGlobalThis.identity` on `<ibgib-identity-header>` initialization using settings.
96
+ - [x] Update `<ibgib-identity-manager>` to subscribe to `EVENT_IBGIB_IDENTITY_CHANGED` and re-render/update its "Set as Active" button state accordingly.
97
97
  - [ ] TODO: Enhance the `<ibgib-identity-header>` component to support selecting/switching between loaded identities directly from a dropdown/dropdown-like UI inside the header.
98
98
 
99
99
  ### Testing Protocol
@@ -464,4 +464,43 @@ h2 {
464
464
  white-space: nowrap;
465
465
  overflow: hidden;
466
466
  text-overflow: ellipsis;
467
+ }
468
+
469
+ /* Delegates Card styling */
470
+ .delegates-table {
471
+ width: 100%;
472
+ border-collapse: collapse;
473
+ margin-top: 1rem;
474
+ font-size: 0.9rem;
475
+ background: rgba(0, 0, 0, 0.2);
476
+ border-radius: var(--radius-sm, 6px);
477
+ overflow: hidden;
478
+ }
479
+
480
+ .delegates-table th,
481
+ .delegates-table td {
482
+ padding: 0.75rem 1rem;
483
+ text-align: left;
484
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
485
+ }
486
+
487
+ .delegates-table th {
488
+ font-family: var(--font-sans);
489
+ font-size: 0.75rem;
490
+ font-weight: 600;
491
+ text-transform: uppercase;
492
+ letter-spacing: 0.05em;
493
+ color: var(--clr-text-secondary, #8fa08f);
494
+ background: rgba(255, 255, 255, 0.02);
495
+ }
496
+
497
+ .delegates-table td {
498
+ font-family: var(--font-mono, monospace);
499
+ font-size: 0.8rem;
500
+ color: var(--clr-text-primary, #ffffff);
501
+ word-break: break-all;
502
+ }
503
+
504
+ .delegates-table tbody tr:hover {
505
+ background: rgba(255, 255, 255, 0.02);
467
506
  }
@@ -85,6 +85,26 @@
85
85
  </div>
86
86
  </div>
87
87
 
88
+ <!-- Delegates Card -->
89
+ <div class="card">
90
+ <h3>Registered Delegates</h3>
91
+ <div id="delegates-container" class="delegates-container">
92
+ <p class="description">Active sub-keystones authorized to perform sync and operations on behalf of this Domain Identity.</p>
93
+ <table class="delegates-table">
94
+ <thead>
95
+ <tr>
96
+ <th>Name / Description</th>
97
+ <th>TJP Address</th>
98
+ <th>Allowed Verbs</th>
99
+ </tr>
100
+ </thead>
101
+ <tbody id="delegates-list">
102
+ <!-- Dynamic list of delegates gets injected here -->
103
+ </tbody>
104
+ </table>
105
+ </div>
106
+ </div>
107
+
88
108
  <!-- Replicas / Sync Peers Repository Manager -->
89
109
  <!-- <div class="card">
90
110
  <h3>Replication & Spaces</h3>
@@ -102,6 +102,7 @@ interface IdentityManagerElements extends ElementsBase {
102
102
  rawAccordionHeaderEl: HTMLDivElement;
103
103
  rawAccordionContentEl: HTMLDivElement;
104
104
  rawKeystoneJsonEl: HTMLPreElement;
105
+ delegatesListEl: HTMLTableSectionElement;
105
106
  }
106
107
 
107
108
  /**
@@ -201,6 +202,7 @@ export class IdentityManagerComponentInstance
201
202
  this.elements.rawAccordionHeaderEl = shadow.getElementById('raw-accordion-header') as HTMLDivElement;
202
203
  this.elements.rawAccordionContentEl = shadow.getElementById('raw-accordion-content') as HTMLDivElement;
203
204
  this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json') as HTMLPreElement;
205
+ this.elements.delegatesListEl = shadow.getElementById('delegates-list') as HTMLTableSectionElement;
204
206
 
205
207
  // ElementBase requires contentEl (points to the main container/body area)
206
208
  this.elements.contentEl = shadow.getElementById('identity-manager-content') as HTMLDivElement;
@@ -524,6 +526,9 @@ export class IdentityManagerComponentInstance
524
526
  this.elements.identityAggrDetailsEl.textContent = '{}';
525
527
  this.elements.scrubberStatusEl.textContent = 'Frame - / -';
526
528
  this.elements.poolsContainerEl.innerHTML = '';
529
+ if (this.elements.delegatesListEl) {
530
+ this.elements.delegatesListEl.innerHTML = '';
531
+ }
527
532
  this.history = [];
528
533
  this.currentFrameIndex = -1;
529
534
  }
@@ -681,6 +686,9 @@ export class IdentityManagerComponentInstance
681
686
 
682
687
  // Render Challenge Pools
683
688
  this.renderChallengePools(keystone.data?.challengePools || []);
689
+
690
+ // Render Delegates
691
+ await this.renderDelegates(keystone.data?.delegates);
684
692
  }
685
693
 
686
694
  private renderChallengePools(pools: any[]) {
@@ -761,6 +769,57 @@ export class IdentityManagerComponentInstance
761
769
  }
762
770
  }
763
771
 
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;
786
+ }
787
+
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
+ }
820
+ }
821
+ }
822
+
764
823
  override async handleContextUpdated(): Promise<void> {
765
824
  await this.renderUI();
766
825
  }
@@ -2,7 +2,7 @@ import thisCss from "./keystone-creator.css";
2
2
  import thisHtml from "./keystone-creator.html";
3
3
  import styleCss from "../../style.css";
4
4
 
5
- import { delay, extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { delay, extractErrorMsg, hash } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
6
  import { HashAlgorithm } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
7
7
  import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
8
8
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
@@ -32,7 +32,8 @@ import { SettingsType } from "@ibgib/web-gib/dist/common/settings/settings-const
32
32
  import { SettingsIbGib_V1 } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs";
33
33
  import { updateSpecialIndex, mut8Timeline } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
34
34
  import { toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
35
- import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
35
+ import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
36
+ import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
36
37
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
37
38
 
38
39
  export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
@@ -247,12 +248,13 @@ export class KeystoneCreatorComponentInstance
247
248
  loadingText: 'Generating...'
248
249
  });
249
250
 
250
- // 1. Prepare Keystone Configs using the test behavior profile (with username and description)
251
- const builder = KeystoneProfileBuilder.buildKeystone('test')
251
+ // 1. Prepare Primary/Domain Keystone Configs
252
+ const builder = KeystoneProfileBuilder.buildKeystone('domain')
252
253
  .withUsername(username)
253
254
  .withDescription(description)
254
255
  .withDetails({
255
256
  client: 'space-gib-web',
257
+ role: 'domain-identity'
256
258
  });
257
259
  const configs = await builder.compileConfigs();
258
260
 
@@ -280,20 +282,66 @@ export class KeystoneCreatorComponentInstance
280
282
  const addr = getIbGibAddr({ ibGib: keystoneIbGib });
281
283
  console.log(`${lc} [NAG][ibgib-paradigm] local genesis complete: ${addr}`);
282
284
  await delay(humanEyeDelay); // for human eyes, not needed for functionality
283
- this.setStatus("Local genesis complete. Syncing to server...", "info");
285
+ this.setStatus("Local genesis complete. Creating sync delegate...", "info");
284
286
  devLog(`✓ Local genesis complete: ${addr}`);
285
287
 
286
- // Expose for dev tools
287
- (window as any).dev_domainI = keystoneIbGib;
288
- (window as any).dev_domainIMasterSecret = masterSecret;
288
+ // Automatically generate Sync Delegate Keystone
289
+ this.showBusy({
290
+ isBusy: true,
291
+ title: 'Creating Sync Delegate...',
292
+ msg: 'Generating cryptographic sync delegate keys...',
293
+ animationEmoji: '🔑'
294
+ });
295
+
296
+ const delegateSecret = await deriveDelegateSecret({ masterSecret });
297
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
298
+ .withUsername(`${username}-sync`)
299
+ .withDescription(`Sync Delegate for ${username}`)
300
+ .withDetails({
301
+ client: 'space-gib-web',
302
+ role: 'sync-delegate'
303
+ });
304
+ const delegateConfigs = await delegateBuilder.compileConfigs();
305
+
306
+ const delegateKeystoneIbGib = await keystoneService.genesis({
307
+ masterSecret: delegateSecret,
308
+ configs: delegateConfigs,
309
+ metaspace,
310
+ space,
311
+ frameDetails: delegateBuilder.getFrameDetails()
312
+ });
313
+
314
+ const delegateAddr = getIbGibAddr({ ibGib: delegateKeystoneIbGib });
315
+ console.log(`${lc} sync delegate genesis complete: ${delegateAddr}`);
316
+ await delay(humanEyeDelay);
317
+
318
+ // Register delegate on parent
319
+ this.setStatus("Registering delegate on parent...", "info");
320
+ this.showBusy({
321
+ isBusy: true,
322
+ title: 'Registering Delegate...',
323
+ msg: 'Evolving domain identity to register delegate...',
324
+ animationEmoji: '📝'
325
+ });
326
+ const evolvedParentKeystoneIbGib = await keystoneService.registerDelegate({
327
+ parentKeystone: keystoneIbGib,
328
+ delegateKeystone: delegateKeystoneIbGib,
329
+ masterSecret,
330
+ metaspace,
331
+ space,
332
+ allowedVerbs: [KEYSTONE_VERB_SYNC]
333
+ });
334
+ const evolvedParentAddr = getIbGibAddr({ ibGib: evolvedParentKeystoneIbGib });
335
+ console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
336
+ await delay(humanEyeDelay);
289
337
 
290
338
  // 4. Post to Server
291
339
  devLog(`Posting genesis keystone to server...`);
340
+ this.setStatus("Syncing genesis to server...", "info");
292
341
  this.showBusy({
293
342
  isBusy: true,
294
343
  title: 'Syncing to Server...',
295
344
  msg: [
296
- 'Local genesis complete!',
297
345
  'Posting genesis keystone to server sync node...',
298
346
  '(may take awhile, depending on strength of keystone...)',
299
347
  ].join('\n'),
@@ -306,6 +354,25 @@ export class KeystoneCreatorComponentInstance
306
354
  return; /* <<<< returns early */
307
355
  }
308
356
 
357
+ // Post evolved keystone + delegate keystone
358
+ this.setStatus("Syncing delegate registration to server...", "info");
359
+ this.showBusy({
360
+ isBusy: true,
361
+ title: 'Syncing Delegate...',
362
+ msg: 'Posting evolved domain identity and sync delegate to server...',
363
+ animationEmoji: '🔄'
364
+ });
365
+ const resEvolve = await spaceGibApiBridge.putEvolveKeystone(
366
+ addr,
367
+ evolvedParentKeystoneIbGib,
368
+ [delegateKeystoneIbGib]
369
+ );
370
+ if (!resEvolve.success) {
371
+ this.setStatus(`Delegate registration sync failed: ${resEvolve.message}`, "error");
372
+ devLog(`✗ Server rejected delegate registration: ${resEvolve.message}`);
373
+ return; /* <<<< returns early */
374
+ }
375
+
309
376
  // 5. Register with the keystones special index
310
377
  this.showBusy({
311
378
  isBusy: true,
@@ -319,25 +386,34 @@ export class KeystoneCreatorComponentInstance
319
386
  rel8nInfos: [
320
387
  {
321
388
  rel8nName: "keystone",
322
- ibGibs: [toDto({ ibGib: keystoneIbGib })],
389
+ ibGibs: [
390
+ toDto({ ibGib: evolvedParentKeystoneIbGib }),
391
+ toDto({ ibGib: delegateKeystoneIbGib })
392
+ ],
323
393
  },
324
394
  ],
325
395
  metaspace,
326
396
  space,
327
397
  });
328
398
 
399
+ // Expose for dev tools
400
+ (window as any).dev_domainI = evolvedParentKeystoneIbGib;
401
+ (window as any).dev_domainIMasterSecret = masterSecret;
402
+ (window as any).dev_syncDelegate = delegateKeystoneIbGib;
403
+ (window as any).dev_syncDelegateSecret = delegateSecret;
404
+
329
405
  // 6. Request to make the new identity active if the checkbox is checked
330
406
  const makeActive = this.elements!.checkboxMakeActive?.checked;
331
407
  if (makeActive) {
332
408
  window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
333
- detail: { activeIdentityAddr: addr },
409
+ detail: { activeIdentityAddr: evolvedParentAddr },
334
410
  bubbles: true,
335
411
  composed: true
336
412
  }));
337
413
  }
338
414
 
339
415
  // 7. Success
340
- this.elements!.keystoneAddrEl.textContent = addr;
416
+ this.elements!.keystoneAddrEl.textContent = evolvedParentAddr;
341
417
  this.elements!.statusArea.classList.remove('hidden');
342
418
  this.setStatus("Keystone successfully created and synced!", "success");
343
419
  await delay(humanEyeDelay); // for human eyes, not needed for functionality