From c4b85a82e6c7230e5efb7e1d86407e54ef90bcb0 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 1 Apr 2026 14:46:18 -0700 Subject: [PATCH] feat(rnetwork): graph visualization polish, delegation sync, layers persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — UX Polish: loading spinner, node hover glow (THREE.RingGeometry), empty state, live slider labels, keyboard shortcuts (Esc/F/T/L), toast notifications, touch-friendly sizing (@media <=768px). Phase 2 — Live Data Integration: edit delegation flow (POST vs PATCH), cross-component sync via CustomEvent("delegations-updated"), dynamic getAuthUrl() fallback chain, sparkline weight tracking via trust events, revocation-aware time slider, new GET /api/trust/events endpoint, include_revoked + revokedAt support on /api/delegations/space. Phase 3 — New Visualizations: BFS delegation path tracing (max depth 3), node/edge opacity dimming for non-path elements, transitive chain indicators (TorusGeometry when >30% received weight), network metrics sidebar (Gini concentration index, top 5 influencers with click-to-focus), log-scale flow thickness (1.5 + log10(1 + w*9) * 3). Phase 4 — Layers Persistence: LayerConfig + CrossLayerFlowConfig schema types, CRDT doc version bump to 2 with migration, saveLayerConfig/ getLayerConfig in local-first-client, auto-persist on rebuildLayerGraph, restore-on-connect, onEngineTick sine-wave pulse animation for compatible feed targets during wiring, wiring progress indicator banner. Co-Authored-By: Claude Opus 4.6 --- modules/rnetwork/components/folk-crm-view.ts | 15 +- .../components/folk-delegation-manager.ts | 72 ++- .../rnetwork/components/folk-graph-viewer.ts | 480 +++++++++++++++++- .../rnetwork/components/folk-trust-sankey.ts | 86 +++- modules/rnetwork/local-first-client.ts | 21 +- modules/rnetwork/schemas.ts | 29 +- src/encryptid/server.ts | 53 +- 7 files changed, 711 insertions(+), 45 deletions(-) diff --git a/modules/rnetwork/components/folk-crm-view.ts b/modules/rnetwork/components/folk-crm-view.ts index d164ed2..ad82502 100644 --- a/modules/rnetwork/components/folk-crm-view.ts +++ b/modules/rnetwork/components/folk-crm-view.ts @@ -172,6 +172,17 @@ class FolkCrmView extends HTMLElement { return match ? match[0] : ""; } + private getAuthUrl(): string { + // Check attribute first, then meta tag, then location-based fallback + const attr = this.getAttribute('auth-url'); + if (attr) return attr; + const meta = document.querySelector('meta[name="encryptid-url"]'); + if (meta) return meta.getAttribute('content') || ''; + // Fallback: same origin for dev, auth subdomain for production + if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return ''; + return `https://auth.${location.hostname.split('.').slice(-2).join('.')}`; + } + private async loadData() { const base = this.getApiBase(); try { @@ -690,10 +701,10 @@ class FolkCrmView extends HTMLElement { return `
- +
- +
`; } diff --git a/modules/rnetwork/components/folk-delegation-manager.ts b/modules/rnetwork/components/folk-delegation-manager.ts index 4a1f62d..8babcc3 100644 --- a/modules/rnetwork/components/folk-delegation-manager.ts +++ b/modules/rnetwork/components/folk-delegation-manager.ts @@ -142,20 +142,32 @@ class FolkDelegationManager extends HTMLElement { const authBase = this.getAuthBase(); try { - const body = JSON.stringify({ - delegateDid: this.modalDelegate, - authority: this.modalAuthority, - weight: this.modalWeight / 100, - spaceSlug: this.space, - maxDepth: this.modalMaxDepth, - retainAuthority: this.modalRetainAuthority, - }); + let res: Response; + if (this.editingId) { + // PATCH existing delegation + const body = JSON.stringify({ + weight: this.modalWeight / 100, + maxDepth: this.modalMaxDepth, + retainAuthority: this.modalRetainAuthority, + }); + res = await fetch(`${authBase}/api/delegations/${this.editingId}`, { method: "PATCH", headers, body }); + } else { + // POST new delegation + const body = JSON.stringify({ + delegateDid: this.modalDelegate, + authority: this.modalAuthority, + weight: this.modalWeight / 100, + spaceSlug: this.space, + maxDepth: this.modalMaxDepth, + retainAuthority: this.modalRetainAuthority, + }); + res = await fetch(`${authBase}/api/delegations`, { method: "POST", headers, body }); + } - const res = await fetch(`${authBase}/api/delegations`, { method: "POST", headers, body }); const data = await res.json(); if (!res.ok) { - this.error = data.error || "Failed to create delegation"; + this.error = data.error || "Failed to save delegation"; this.render(); return; } @@ -164,8 +176,14 @@ class FolkDelegationManager extends HTMLElement { this.editingId = null; this.error = ""; await this.loadData(); + + // Dispatch cross-component sync event + this.dispatchEvent(new CustomEvent("delegations-updated", { + bubbles: true, composed: true, + detail: { space: this.space }, + })); } catch { - this.error = "Network error creating delegation"; + this.error = "Network error saving delegation"; this.render(); } } @@ -188,6 +206,7 @@ class FolkDelegationManager extends HTMLElement { return; } await this.loadData(); + this.dispatchEvent(new CustomEvent("delegations-updated", { bubbles: true, composed: true, detail: { space: this.space } })); } catch { this.error = "Network error"; this.render(); @@ -207,6 +226,7 @@ class FolkDelegationManager extends HTMLElement { return; } await this.loadData(); + this.dispatchEvent(new CustomEvent("delegations-updated", { bubbles: true, composed: true, detail: { space: this.space } })); } catch { this.error = "Network error"; this.render(); @@ -239,6 +259,7 @@ class FolkDelegationManager extends HTMLElement { ${this.esc(this.getUserName(d.delegateDid))} ${Math.round(d.weight * 100)}% ${d.state} + ${d.state === 'active' ? ` ` : d.state === 'paused' ? ` @@ -275,11 +296,11 @@ class FolkDelegationManager extends HTMLElement { ${this.users.map(u => ``).join("")} - + -
Available: ${maxWeight}% of ${this.modalAuthority}
+
Available: ${maxWeight}% of ${DM_AUTHORITY_DISPLAY[this.modalAuthority]?.label || this.modalAuthority}
- +