@coreason-ai/sensory-core 1.4.0 → 1.5.0
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.
- package/CHANGELOG.md +16 -0
- package/dist/components/DlpParticleShield.d.ts +9 -0
- package/dist/components/DlpParticleShield.js +197 -0
- package/dist/components/KeysigningCeremonyModal.d.ts +25 -0
- package/dist/components/KeysigningCeremonyModal.js +139 -0
- package/dist/components/SemanticConstellationMap.d.ts +23 -0
- package/dist/components/SemanticConstellationMap.js +273 -0
- package/dist/components/TemporalScrubber.d.ts +3 -0
- package/dist/components/TemporalScrubber.js +19 -7
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0](https://github.com/CoReason-AI/coreason-sensory-core/compare/v1.4.0...v1.5.0) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **auth:** initiate sovereign keysigning ceremony modal primitives for operator handovers ref CoReason-AI/coreason-urn-authority[#104](https://github.com/CoReason-AI/coreason-sensory-core/issues/104) ([d8c928d](https://github.com/CoReason-AI/coreason-sensory-core/commit/d8c928d006055c2838734a10aa5522be19e461a0))
|
|
14
|
+
* **sensory-core:** integrate temporal scrubber active fork controls ref [#106](https://github.com/CoReason-AI/coreason-sensory-core/issues/106) ([861acc8](https://github.com/CoReason-AI/coreason-sensory-core/commit/861acc8605940df057b663fbd1994c3071ea4ac5))
|
|
15
|
+
* **sensory:** implement egress classification boundary and particle animations ([#105](https://github.com/CoReason-AI/coreason-sensory-core/issues/105)) ([80559e1](https://github.com/CoReason-AI/coreason-sensory-core/commit/80559e19458766ef418426c4aae605a7a263367f))
|
|
16
|
+
* **sensory:** implement SemanticConstellationMap visualizer ([#109](https://github.com/CoReason-AI/coreason-sensory-core/issues/109)) ([b31efec](https://github.com/CoReason-AI/coreason-sensory-core/commit/b31efec70c6c544cfb0cb00f6e1bef98a0a3cf9c))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **github:** add explicit repo targeting and merge fallbacks to release workflow ([d11152b](https://github.com/CoReason-AI/coreason-sensory-core/commit/d11152b5e38493553caa5a99e20a73d3de694661))
|
|
22
|
+
* **github:** parse release-please pr output JSON for automerge ([6e5af70](https://github.com/CoReason-AI/coreason-sensory-core/commit/6e5af70d5fe74a304bacb6ba28433a0e3fdbd926))
|
|
23
|
+
|
|
8
24
|
## [1.4.0](https://github.com/CoReason-AI/coreason-sensory-core/compare/v1.3.0...v1.4.0) (2026-05-22)
|
|
9
25
|
|
|
10
26
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface DlpParticleShieldProps {
|
|
3
|
+
isBlocked: boolean;
|
|
4
|
+
blockedDataPattern?: string;
|
|
5
|
+
destinationLabel?: string;
|
|
6
|
+
onShieldAlert?: () => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const DlpParticleShield: React.FC<DlpParticleShieldProps>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Copyright (c) 2026 CoReason, Inc
|
|
2
|
+
//
|
|
3
|
+
// This software is proprietary and dual-licensed
|
|
4
|
+
// Licensed under the Prosperity Public License 3.0 (the "License")
|
|
5
|
+
// A copy of the license is available at <https://prosperitylicense.com/versions/3.0.0>
|
|
6
|
+
// For details, see the LICENSE file
|
|
7
|
+
// Commercial use beyond a 30-day trial requires a separate license
|
|
8
|
+
//
|
|
9
|
+
// Source Code: <https://github.com/CoReason-AI/coreason-runtime>
|
|
10
|
+
import React, { useRef, useEffect } from 'react';
|
|
11
|
+
import { GlassBox } from './GlassBox';
|
|
12
|
+
import { Shield, AlertTriangle, Globe, Activity } from 'lucide-react';
|
|
13
|
+
export const DlpParticleShield = ({ isBlocked, blockedDataPattern = 'SPIFFE-SVID Private Key Match', destinationLabel = 'api.openai.com/v1/chat', onShieldAlert, className = '' }) => {
|
|
14
|
+
const canvasRef = useRef(null);
|
|
15
|
+
const isBlockedRef = useRef(isBlocked);
|
|
16
|
+
// Synchronize block state reference for the animation loop context
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (isBlocked && !isBlockedRef.current) {
|
|
19
|
+
if (onShieldAlert) {
|
|
20
|
+
onShieldAlert();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
isBlockedRef.current = isBlocked;
|
|
24
|
+
}, [isBlocked, onShieldAlert]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const canvas = canvasRef.current;
|
|
27
|
+
if (!canvas)
|
|
28
|
+
return;
|
|
29
|
+
const ctx = canvas.getContext('2d');
|
|
30
|
+
if (!ctx)
|
|
31
|
+
return;
|
|
32
|
+
let animationFrameId;
|
|
33
|
+
const particles = [];
|
|
34
|
+
// Adjust canvas dimensions for screen resolution DPI
|
|
35
|
+
const resizeCanvas = () => {
|
|
36
|
+
const rect = canvas.getBoundingClientRect();
|
|
37
|
+
canvas.width = rect.width * window.devicePixelRatio;
|
|
38
|
+
canvas.height = rect.height * window.devicePixelRatio;
|
|
39
|
+
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
40
|
+
};
|
|
41
|
+
resizeCanvas();
|
|
42
|
+
window.addEventListener('resize', resizeCanvas);
|
|
43
|
+
// Main animation loop
|
|
44
|
+
const animate = () => {
|
|
45
|
+
const w = canvas.width / window.devicePixelRatio;
|
|
46
|
+
const h = canvas.height / window.devicePixelRatio;
|
|
47
|
+
// Wipe current frame
|
|
48
|
+
ctx.clearRect(0, 0, w, h);
|
|
49
|
+
// Node positions
|
|
50
|
+
const srcX = w * 0.15;
|
|
51
|
+
const srcY = h * 0.5;
|
|
52
|
+
const dstX = w * 0.85;
|
|
53
|
+
const dstY = h * 0.5;
|
|
54
|
+
const shieldX = w * 0.55;
|
|
55
|
+
// Generate new egress particles
|
|
56
|
+
if (particles.length < 120 && Math.random() < 0.3) {
|
|
57
|
+
particles.push({
|
|
58
|
+
x: srcX,
|
|
59
|
+
y: srcY + (Math.random() - 0.5) * 16,
|
|
60
|
+
vx: 2 + Math.random() * 2,
|
|
61
|
+
vy: (Math.random() - 0.5) * 1.0,
|
|
62
|
+
alpha: 0.8 + Math.random() * 0.2,
|
|
63
|
+
size: 1.5 + Math.random() * 2,
|
|
64
|
+
color: 'rgb(34, 211, 238)', // Cyan
|
|
65
|
+
isSplashing: false
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Draw Connection Conduit Path
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.moveTo(srcX, srcY);
|
|
71
|
+
ctx.bezierCurveTo(w * 0.35, srcY - 30, w * 0.65, srcY + 30, dstX, dstY);
|
|
72
|
+
ctx.strokeStyle = isBlockedRef.current
|
|
73
|
+
? 'rgba(239, 68, 68, 0.15)'
|
|
74
|
+
: 'rgba(34, 211, 238, 0.15)';
|
|
75
|
+
ctx.lineWidth = 2;
|
|
76
|
+
ctx.stroke();
|
|
77
|
+
// Transition and draw active particles
|
|
78
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
79
|
+
const p = particles[i];
|
|
80
|
+
// Move particle
|
|
81
|
+
p.x += p.vx;
|
|
82
|
+
p.y += p.vy;
|
|
83
|
+
// Apply sine wave path variance if nominal
|
|
84
|
+
if (!p.isSplashing) {
|
|
85
|
+
p.y += Math.sin(p.x * 0.05) * 0.25;
|
|
86
|
+
}
|
|
87
|
+
// Boundary checks and collision detection
|
|
88
|
+
const hasReachedBoundary = p.x >= dstX;
|
|
89
|
+
const hasCollidedWithShield = isBlockedRef.current && p.x >= shieldX && !p.isSplashing;
|
|
90
|
+
if (hasReachedBoundary) {
|
|
91
|
+
// Egress completed successfully
|
|
92
|
+
particles.splice(i, 1);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (hasCollidedWithShield) {
|
|
96
|
+
// Trigger backward splash deflection
|
|
97
|
+
p.isSplashing = true;
|
|
98
|
+
p.vx = -p.vx * (0.3 + Math.random() * 0.4);
|
|
99
|
+
p.vy = (Math.random() - 0.5) * 5;
|
|
100
|
+
p.color = 'rgb(239, 68, 68)'; // Transmute to Crimson Red
|
|
101
|
+
}
|
|
102
|
+
// Reduce alpha over time for deflecting particles
|
|
103
|
+
if (p.isSplashing) {
|
|
104
|
+
p.alpha -= 0.035;
|
|
105
|
+
// Apply slight gravity drift
|
|
106
|
+
p.vy += 0.08;
|
|
107
|
+
}
|
|
108
|
+
// Discard particles that are out of visual bounds or transparent
|
|
109
|
+
if (p.alpha <= 0 || p.x < 0) {
|
|
110
|
+
particles.splice(i, 1);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Render individual particle
|
|
114
|
+
ctx.beginPath();
|
|
115
|
+
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
116
|
+
ctx.fillStyle = p.color.replace('rgb', 'rgba').replace(')', `, ${p.alpha})`);
|
|
117
|
+
ctx.shadowColor = p.color;
|
|
118
|
+
ctx.shadowBlur = p.isSplashing ? 6 : 3;
|
|
119
|
+
ctx.fill();
|
|
120
|
+
// Reset shadow configuration for next drawing commands
|
|
121
|
+
ctx.shadowBlur = 0;
|
|
122
|
+
}
|
|
123
|
+
// Draw operational endpoints nodes
|
|
124
|
+
// 1. Source Enclave Node
|
|
125
|
+
ctx.beginPath();
|
|
126
|
+
ctx.arc(srcX, srcY, 8, 0, Math.PI * 2);
|
|
127
|
+
ctx.fillStyle = '#0f172a';
|
|
128
|
+
ctx.strokeStyle = '#22d3ee';
|
|
129
|
+
ctx.lineWidth = 2.5;
|
|
130
|
+
ctx.stroke();
|
|
131
|
+
ctx.fill();
|
|
132
|
+
// Node Inner status pulse
|
|
133
|
+
ctx.beginPath();
|
|
134
|
+
ctx.arc(srcX, srcY, 3, 0, Math.PI * 2);
|
|
135
|
+
ctx.fillStyle = '#22d3ee';
|
|
136
|
+
ctx.fill();
|
|
137
|
+
// 2. Gateway Node
|
|
138
|
+
ctx.beginPath();
|
|
139
|
+
ctx.arc(dstX, dstY, 8, 0, Math.PI * 2);
|
|
140
|
+
ctx.fillStyle = '#0f172a';
|
|
141
|
+
ctx.strokeStyle = isBlockedRef.current ? '#ef4444' : '#10b981';
|
|
142
|
+
ctx.lineWidth = 2.5;
|
|
143
|
+
ctx.stroke();
|
|
144
|
+
ctx.fill();
|
|
145
|
+
// Draw Glass Box Shield Boundary Barrier if active
|
|
146
|
+
if (isBlockedRef.current) {
|
|
147
|
+
ctx.beginPath();
|
|
148
|
+
ctx.moveTo(shieldX, h * 0.15);
|
|
149
|
+
ctx.lineTo(shieldX, h * 0.85);
|
|
150
|
+
ctx.strokeStyle = 'rgba(239, 68, 68, 0.7)';
|
|
151
|
+
ctx.lineWidth = 3;
|
|
152
|
+
ctx.setLineDash([4, 4]);
|
|
153
|
+
ctx.shadowColor = '#ef4444';
|
|
154
|
+
ctx.shadowBlur = 8;
|
|
155
|
+
ctx.stroke();
|
|
156
|
+
ctx.setLineDash([]); // Reset dash config
|
|
157
|
+
ctx.shadowBlur = 0;
|
|
158
|
+
// Draw Shield Label banner
|
|
159
|
+
ctx.fillStyle = '#ef4444';
|
|
160
|
+
ctx.font = 'bold 8px monospace';
|
|
161
|
+
ctx.textAlign = 'center';
|
|
162
|
+
ctx.fillText('AIR-GAP SHIELD ACTIVE', shieldX, h * 0.1);
|
|
163
|
+
}
|
|
164
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
165
|
+
};
|
|
166
|
+
animate();
|
|
167
|
+
return () => {
|
|
168
|
+
cancelAnimationFrame(animationFrameId);
|
|
169
|
+
window.removeEventListener('resize', resizeCanvas);
|
|
170
|
+
};
|
|
171
|
+
}, []);
|
|
172
|
+
return (React.createElement(GlassBox, { density: "dense", className: `border bg-black/60 backdrop-blur-md p-5 flex flex-col gap-4 rounded-xl transition-all duration-500 ${isBlocked
|
|
173
|
+
? 'border-red-500/50 shadow-[0_0_20px_rgba(239,68,68,0.15)]'
|
|
174
|
+
: 'border-white/10 shadow-[0_4px_15px_rgba(0,0,0,0.5)]'} ${className}` },
|
|
175
|
+
React.createElement("div", { className: "flex items-center justify-between border-b border-white/10 pb-2" },
|
|
176
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
177
|
+
React.createElement(Shield, { className: `w-4 h-4 ${isBlocked ? 'text-red-400 animate-pulse' : 'text-cyan-400'}` }),
|
|
178
|
+
React.createElement("span", { className: "text-[11px] font-bold text-gray-400 uppercase tracking-widest" }, "DLP Particle Shield")),
|
|
179
|
+
React.createElement("div", { className: "flex items-center gap-1.5 font-mono text-[9px]" },
|
|
180
|
+
React.createElement(Activity, { className: `w-3.5 h-3.5 ${isBlocked ? 'text-red-400' : 'text-green-400 animate-pulse'}` }),
|
|
181
|
+
React.createElement("span", { className: isBlocked ? 'text-red-400 font-bold' : 'text-green-400 font-bold' }, isBlocked ? 'BARRIER_ENGAGED' : 'FLOW_NOMINAL'))),
|
|
182
|
+
React.createElement("div", { className: "relative w-full h-36 bg-black/30 border border-white/5 rounded-lg overflow-hidden" },
|
|
183
|
+
React.createElement("canvas", { ref: canvasRef, className: "w-full h-full block" }),
|
|
184
|
+
React.createElement("div", { className: "absolute top-2 left-3 pointer-events-none select-none font-mono text-[8px] text-gray-500" }, "LOCAL ENCLAVE"),
|
|
185
|
+
React.createElement("div", { className: "absolute top-2 right-3 pointer-events-none select-none font-mono text-[8px] text-gray-500 text-right" }, "DESTINATION GATEWAY")),
|
|
186
|
+
React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 font-mono text-[9px] bg-black/35 rounded-lg border border-white/5 p-3" },
|
|
187
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
188
|
+
React.createElement("span", { className: "text-gray-500 uppercase tracking-wider" }, "Egress Target"),
|
|
189
|
+
React.createElement("div", { className: "flex items-center gap-1.5 text-gray-200 font-bold truncate" },
|
|
190
|
+
React.createElement(Globe, { className: "w-3.5 h-3.5 text-gray-400 shrink-0" }),
|
|
191
|
+
React.createElement("span", { className: "truncate", title: destinationLabel }, destinationLabel))),
|
|
192
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
193
|
+
React.createElement("span", { className: "text-gray-500 uppercase tracking-wider" }, "Blocked Classification Pattern"),
|
|
194
|
+
React.createElement("div", { className: "flex items-center gap-1.5 text-gray-200 font-bold truncate" },
|
|
195
|
+
React.createElement(AlertTriangle, { className: `w-3.5 h-3.5 shrink-0 ${isBlocked ? 'text-red-400' : 'text-gray-500'}` }),
|
|
196
|
+
React.createElement("span", { className: "truncate", title: blockedDataPattern }, isBlocked ? blockedDataPattern : 'N/A'))))));
|
|
197
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface DelegatedAttestationReceipt {
|
|
3
|
+
sessionToken: string;
|
|
4
|
+
attestationHash: string;
|
|
5
|
+
signaturesCollected: number;
|
|
6
|
+
signaturesThreshold: number;
|
|
7
|
+
status: 'PENDING' | 'ESTABLISHED' | 'RETRACTED' | 'FAULT';
|
|
8
|
+
timestamp: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SignerProfile {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
spiffeId: string;
|
|
14
|
+
status: 'PENDING' | 'SIGNED' | 'REJECTED';
|
|
15
|
+
}
|
|
16
|
+
export interface KeysigningCeremonyModalProps {
|
|
17
|
+
isOpen: boolean;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
sessionToken: string;
|
|
20
|
+
requiredSigners?: SignerProfile[];
|
|
21
|
+
threshold?: number;
|
|
22
|
+
onComplete?: (receipt: DelegatedAttestationReceipt) => void;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare const KeysigningCeremonyModal: React.FC<KeysigningCeremonyModalProps>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Copyright (c) 2026 CoReason, Inc
|
|
2
|
+
//
|
|
3
|
+
// This software is proprietary and dual-licensed
|
|
4
|
+
// Licensed under the Prosperity Public License 3.0 (the "License")
|
|
5
|
+
// A copy of the license is available at <https://prosperitylicense.com/versions/3.0.0>
|
|
6
|
+
// For details, see the LICENSE file
|
|
7
|
+
// Commercial use beyond a 30-day trial requires a separate license
|
|
8
|
+
//
|
|
9
|
+
// Source Code: <https://github.com/CoReason-AI/coreason-sensory-core>
|
|
10
|
+
import React, { useState, useEffect } from 'react';
|
|
11
|
+
import { GlassBox } from './GlassBox';
|
|
12
|
+
import { Shield, ShieldAlert, X, Check, Activity } from 'lucide-react';
|
|
13
|
+
const DEFAULT_REQUIRED_SIGNERS = [
|
|
14
|
+
{ id: 'alice', name: 'Alice (Security)', spiffeId: 'spiffe://coreason.internal/tenant/operator/alice', status: 'PENDING' },
|
|
15
|
+
{ id: 'bob', name: 'Bob (Audit)', spiffeId: 'spiffe://coreason.internal/tenant/operator/bob', status: 'PENDING' }
|
|
16
|
+
];
|
|
17
|
+
export const KeysigningCeremonyModal = ({ isOpen, onClose, sessionToken, requiredSigners = DEFAULT_REQUIRED_SIGNERS, threshold = 2, onComplete, className = '' }) => {
|
|
18
|
+
var _a, _b, _c, _d;
|
|
19
|
+
const [signersList, setSignersList] = useState(requiredSigners);
|
|
20
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
21
|
+
const [ceremonyStatus, setCeremonyStatus] = useState('PENDING');
|
|
22
|
+
const [panicMessage, setPanicMessage] = useState(null);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (isOpen) {
|
|
25
|
+
setSignersList(requiredSigners);
|
|
26
|
+
setCeremonyStatus('PENDING');
|
|
27
|
+
setPanicMessage(null);
|
|
28
|
+
setIsProcessing(false);
|
|
29
|
+
}
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [isOpen]);
|
|
32
|
+
if (!isOpen)
|
|
33
|
+
return null;
|
|
34
|
+
const signedCount = signersList.filter(s => s.status === 'SIGNED').length;
|
|
35
|
+
const isFused = signedCount >= threshold;
|
|
36
|
+
const handleSign = (signerId) => {
|
|
37
|
+
if (isProcessing || ceremonyStatus === 'ESTABLISHED')
|
|
38
|
+
return;
|
|
39
|
+
setSignersList(prev => prev.map(s => (s.id === signerId ? Object.assign(Object.assign({}, s), { status: 'SIGNED' }) : s)));
|
|
40
|
+
};
|
|
41
|
+
const handleRetract = (signerId) => {
|
|
42
|
+
if (isProcessing || ceremonyStatus === 'ESTABLISHED')
|
|
43
|
+
return;
|
|
44
|
+
setSignersList(prev => prev.map(s => (s.id === signerId ? Object.assign(Object.assign({}, s), { status: 'PENDING' }) : s)));
|
|
45
|
+
};
|
|
46
|
+
const handleFinalize = () => {
|
|
47
|
+
if (signedCount < threshold) {
|
|
48
|
+
setPanicMessage('Signature threshold not satisfied.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setIsProcessing(true);
|
|
52
|
+
setPanicMessage(null);
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
setIsProcessing(false);
|
|
55
|
+
setCeremonyStatus('ESTABLISHED');
|
|
56
|
+
const receipt = {
|
|
57
|
+
sessionToken,
|
|
58
|
+
attestationHash: `0x${Array.from({ length: 64 }, () => Math.floor(Math.random() * 16).toString(16)).join('')}`,
|
|
59
|
+
signaturesCollected: signedCount,
|
|
60
|
+
signaturesThreshold: threshold,
|
|
61
|
+
status: 'ESTABLISHED',
|
|
62
|
+
timestamp: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
if (onComplete) {
|
|
65
|
+
onComplete(receipt);
|
|
66
|
+
}
|
|
67
|
+
}, 1800);
|
|
68
|
+
};
|
|
69
|
+
const resetCeremony = () => {
|
|
70
|
+
setSignersList(requiredSigners);
|
|
71
|
+
setCeremonyStatus('PENDING');
|
|
72
|
+
setPanicMessage(null);
|
|
73
|
+
};
|
|
74
|
+
return (React.createElement("div", { className: `fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm ${className}`, "data-testid": "keysigning-modal-overlay" },
|
|
75
|
+
React.createElement(GlassBox, { density: "dense", className: "w-full max-w-lg border border-white/10 bg-[#0c0c0f]/90 text-white rounded-xl shadow-2xl overflow-hidden flex flex-col font-mono" },
|
|
76
|
+
React.createElement("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/10 bg-black/40" },
|
|
77
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
78
|
+
React.createElement(Shield, { className: "w-4 h-4 text-amber-500" }),
|
|
79
|
+
React.createElement("span", { className: "text-[11px] font-bold text-gray-400 uppercase tracking-widest" }, "Sovereign Keysigning Ceremony")),
|
|
80
|
+
React.createElement("button", { onClick: onClose, className: "p-1 hover:bg-white/10 rounded transition-colors text-gray-400 hover:text-white", title: "Cancel Ceremony" },
|
|
81
|
+
React.createElement(X, { className: "w-4 h-4" }))),
|
|
82
|
+
React.createElement("div", { className: "p-6 flex flex-col items-center gap-6 overflow-y-auto max-h-[75vh]" },
|
|
83
|
+
React.createElement("div", { className: "relative w-40 h-40 flex items-center justify-center" },
|
|
84
|
+
React.createElement("div", { className: `absolute inset-0 rounded-full border border-dashed transition-all duration-1000 ${ceremonyStatus === 'ESTABLISHED'
|
|
85
|
+
? 'border-yellow-500/50 scale-110 rotate-45'
|
|
86
|
+
: 'border-white/10'}` }),
|
|
87
|
+
React.createElement("svg", { viewBox: "0 0 120 120", className: "w-full h-full drop-shadow-[0_0_15px_rgba(217,119,6,0.3)]" },
|
|
88
|
+
React.createElement("path", { d: "M 60 15 C 75 12, 90 22, 95 35 C 102 48, 108 65, 98 80 C 90 92, 75 105, 60 102 C 45 100, 30 92, 22 80 C 12 65, 18 48, 25 35 C 30 22, 45 18, 60 15 Z", fill: ceremonyStatus === 'ESTABLISHED' ? "#7f1d1d" : "#3b1704", stroke: ceremonyStatus === 'ESTABLISHED' ? "#eab308" : "#854d0e", strokeWidth: "3.5", className: "transition-colors duration-1000" }),
|
|
89
|
+
!isFused && (React.createElement("path", { d: "M 60 22 L 58 48 L 62 72 L 60 98", stroke: "#1c0a00", strokeWidth: "3", fill: "none", className: "opacity-80" })),
|
|
90
|
+
React.createElement("path", { d: "M 32 60 C 42 60, 48 54, 58 48", fill: "none", stroke: ((_a = signersList[0]) === null || _a === void 0 ? void 0 : _a.status) === 'SIGNED' ? "#fbbf24" : "#78350f", strokeWidth: "4", strokeDasharray: "45", strokeDashoffset: ((_b = signersList[0]) === null || _b === void 0 ? void 0 : _b.status) === 'SIGNED' ? "0" : "45", className: "transition-all duration-1000 ease-in-out" }),
|
|
91
|
+
React.createElement("path", { d: "M 88 60 C 78 60, 72 66, 62 72", fill: "none", stroke: ((_c = signersList[1]) === null || _c === void 0 ? void 0 : _c.status) === 'SIGNED' ? "#fbbf24" : "#78350f", strokeWidth: "4", strokeDasharray: "45", strokeDashoffset: ((_d = signersList[1]) === null || _d === void 0 ? void 0 : _d.status) === 'SIGNED' ? "0" : "45", className: "transition-all duration-1000 ease-in-out" }),
|
|
92
|
+
React.createElement("path", { d: "M 53 45 A 7 7 0 1 1 67 45 A 7 7 0 0 1 60 52 L 60 75 L 66 75 M 60 66 L 66 66", fill: "none", stroke: ceremonyStatus === 'ESTABLISHED' ? "#fbbf24" : "#ca8a04", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "transition-colors duration-1000" }),
|
|
93
|
+
ceremonyStatus === 'ESTABLISHED' && (React.createElement("path", { d: "M 50 82 L 60 88 L 70 82 V 76 H 50 Z", fill: "#fbbf24", className: "animate-pulse" }))),
|
|
94
|
+
ceremonyStatus === 'ESTABLISHED' && (React.createElement("div", { className: "absolute inset-2 rounded-full border border-yellow-500 animate-ping opacity-60 pointer-events-none", style: { animationDuration: '1.5s' } }))),
|
|
95
|
+
React.createElement("div", { className: "w-full text-center" },
|
|
96
|
+
React.createElement("h3", { className: "text-sm font-bold text-white uppercase tracking-wider" }, ceremonyStatus === 'ESTABLISHED' ? 'Attestation Established' : isProcessing ? 'Fusing Key Shares...' : 'Signatures Verification'),
|
|
97
|
+
React.createElement("p", { className: "text-[10px] text-gray-400 mt-1" },
|
|
98
|
+
"Threshold requirements: ",
|
|
99
|
+
signedCount,
|
|
100
|
+
" of ",
|
|
101
|
+
threshold,
|
|
102
|
+
" signatures collected.")),
|
|
103
|
+
React.createElement("div", { className: "w-full flex flex-col gap-2.5" }, signersList.map((signer) => {
|
|
104
|
+
const isSigned = signer.status === 'SIGNED';
|
|
105
|
+
return (React.createElement("div", { key: signer.id, className: `flex items-center justify-between p-3 rounded-lg border transition-all ${isSigned
|
|
106
|
+
? 'bg-amber-950/20 border-amber-500/40 text-amber-300'
|
|
107
|
+
: 'bg-black/30 border-white/5 text-gray-400'}` },
|
|
108
|
+
React.createElement("div", { className: "flex flex-col min-w-0 pr-2" },
|
|
109
|
+
React.createElement("span", { className: "text-[10px] font-bold text-white" }, signer.name),
|
|
110
|
+
React.createElement("span", { className: "text-[9px] text-gray-500 truncate mt-0.5" }, signer.spiffeId)),
|
|
111
|
+
React.createElement("div", { className: "flex items-center gap-2" }, isSigned ? (React.createElement(React.Fragment, null,
|
|
112
|
+
React.createElement("span", { className: "text-[9px] font-bold tracking-widest text-amber-500 flex items-center gap-1 uppercase" },
|
|
113
|
+
React.createElement(Check, { className: "w-3 h-3 text-amber-400" }),
|
|
114
|
+
" Signed"),
|
|
115
|
+
React.createElement("button", { disabled: isProcessing || ceremonyStatus === 'ESTABLISHED', onClick: () => handleRetract(signer.id), className: "px-2 py-1 bg-red-950/45 border border-red-900/40 hover:bg-red-900 hover:text-white text-red-400 text-[8px] font-bold uppercase rounded transition-colors disabled:opacity-50" }, "Retract"))) : (React.createElement("button", { disabled: isProcessing || ceremonyStatus === 'ESTABLISHED', onClick: () => handleSign(signer.id), className: "px-3 py-1.5 bg-gradient-to-r from-amber-500 to-yellow-600 hover:from-amber-400 hover:to-yellow-500 text-black text-[9px] font-bold uppercase rounded transition-all active:scale-95 disabled:opacity-50" }, "Sign Key")))));
|
|
116
|
+
})),
|
|
117
|
+
panicMessage && (React.createElement("div", { className: "w-full p-3 bg-red-950/40 border border-red-500/30 text-red-400 rounded-lg flex items-start gap-2" },
|
|
118
|
+
React.createElement(ShieldAlert, { className: "w-4 h-4 shrink-0 mt-0.5" }),
|
|
119
|
+
React.createElement("div", { className: "text-[9px] leading-relaxed uppercase" }, panicMessage))),
|
|
120
|
+
ceremonyStatus === 'ESTABLISHED' && (React.createElement("div", { className: "w-full p-3 bg-black/45 rounded-lg border border-white/5 flex flex-col gap-1 text-[9px] text-gray-400" },
|
|
121
|
+
React.createElement("div", { className: "flex justify-between" },
|
|
122
|
+
React.createElement("span", null, "Session Token:"),
|
|
123
|
+
React.createElement("span", { className: "text-white font-bold" }, sessionToken)),
|
|
124
|
+
React.createElement("div", { className: "flex justify-between" },
|
|
125
|
+
React.createElement("span", null, "Validation Hash:"),
|
|
126
|
+
React.createElement("span", { className: "text-amber-500 font-mono" }, "Attestation verified")),
|
|
127
|
+
React.createElement("div", { className: "flex justify-between" },
|
|
128
|
+
React.createElement("span", null, "Status:"),
|
|
129
|
+
React.createElement("span", { className: "text-green-400 font-bold uppercase" }, "ESTABLISHED"))))),
|
|
130
|
+
React.createElement("div", { className: "px-6 py-4 border-t border-white/10 bg-black/40 flex items-center justify-between" },
|
|
131
|
+
React.createElement("div", { className: "flex items-center gap-1.5 text-[9px] text-gray-500 uppercase" },
|
|
132
|
+
React.createElement(Activity, { className: "w-3.5 h-3.5 animate-pulse text-amber-500" }),
|
|
133
|
+
React.createElement("span", null, "Mtls Handshake Active")),
|
|
134
|
+
React.createElement("div", { className: "flex gap-2" }, ceremonyStatus === 'ESTABLISHED' ? (React.createElement("button", { onClick: onClose, className: "px-4 py-2 bg-gray-900 hover:bg-gray-800 text-white border border-white/10 text-xs font-bold uppercase rounded transition-colors" }, "Close")) : (React.createElement(React.Fragment, null,
|
|
135
|
+
React.createElement("button", { disabled: isProcessing, onClick: resetCeremony, className: "px-3 py-2 bg-transparent hover:bg-white/5 text-gray-400 hover:text-white text-xs font-bold uppercase rounded transition-colors" }, "Reset"),
|
|
136
|
+
React.createElement("button", { disabled: isProcessing || signedCount < threshold, onClick: handleFinalize, className: `px-4 py-2 text-xs font-bold uppercase rounded transition-all active:scale-95 flex items-center gap-1.5 ${signedCount >= threshold
|
|
137
|
+
? 'bg-gradient-to-r from-amber-500 to-yellow-600 hover:from-amber-400 hover:to-yellow-500 text-black shadow-[0_0_15px_rgba(245,158,11,0.2)]'
|
|
138
|
+
: 'bg-gray-900 text-gray-600 border border-gray-800 cursor-not-allowed'}` }, isProcessing ? 'Verifying...' : 'Finalize Seal'))))))));
|
|
139
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SemanticConceptNode {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
z: number;
|
|
8
|
+
group: string;
|
|
9
|
+
weight: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SemanticConceptEdge {
|
|
12
|
+
source: string;
|
|
13
|
+
target: string;
|
|
14
|
+
strength: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SemanticConstellationMapProps {
|
|
17
|
+
nodes: SemanticConceptNode[];
|
|
18
|
+
edges: SemanticConceptEdge[];
|
|
19
|
+
activeNodeId?: string;
|
|
20
|
+
onSelectNode?: (nodeId: string) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const SemanticConstellationMap: React.FC<SemanticConstellationMapProps>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Copyright (c) 2026 CoReason, Inc.
|
|
2
|
+
//
|
|
3
|
+
// This software is proprietary and dual-licensed.
|
|
4
|
+
// Licensed under the Prosperity Public License 3.0.
|
|
5
|
+
import React, { useRef, useEffect, useState } from 'react';
|
|
6
|
+
import { GlassBox, cn } from './GlassBox';
|
|
7
|
+
// Map group names to beautiful sleek neon colors
|
|
8
|
+
const GROUP_COLORS = {
|
|
9
|
+
alignment: '#00ffcc', // CoReason Accent Cyan
|
|
10
|
+
inference: '#a855f7', // Purple
|
|
11
|
+
sensory: '#f43f5e', // Rose
|
|
12
|
+
temporal: '#eab308', // Yellow
|
|
13
|
+
default: '#3b82f6', // Blue
|
|
14
|
+
};
|
|
15
|
+
export const SemanticConstellationMap = ({ nodes, edges, activeNodeId, onSelectNode, className, }) => {
|
|
16
|
+
const canvasRef = useRef(null);
|
|
17
|
+
const containerRef = useRef(null);
|
|
18
|
+
const angleRef = useRef(0);
|
|
19
|
+
const animationFrameIdRef = useRef(null);
|
|
20
|
+
const [hoveredNodeId, setHoveredNodeId] = useState(undefined);
|
|
21
|
+
const [dimensions, setDimensions] = useState({ width: 400, height: 350 });
|
|
22
|
+
// Handle container resizing to fit layout
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!containerRef.current)
|
|
25
|
+
return;
|
|
26
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const { width, height } = entry.contentRect;
|
|
29
|
+
setDimensions({
|
|
30
|
+
width: Math.max(width, 100),
|
|
31
|
+
height: Math.max(height, 100),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
resizeObserver.observe(containerRef.current);
|
|
36
|
+
return () => resizeObserver.disconnect();
|
|
37
|
+
}, []);
|
|
38
|
+
// Compute rotation & render projected coordinates
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const canvas = canvasRef.current;
|
|
41
|
+
if (!canvas)
|
|
42
|
+
return;
|
|
43
|
+
const ctx = canvas.getContext('2d');
|
|
44
|
+
if (!ctx)
|
|
45
|
+
return;
|
|
46
|
+
let isComponentActive = true;
|
|
47
|
+
const renderLoop = () => {
|
|
48
|
+
if (!isComponentActive)
|
|
49
|
+
return;
|
|
50
|
+
// Adjust rotation angle over time
|
|
51
|
+
angleRef.current += 0.003;
|
|
52
|
+
const angle = angleRef.current;
|
|
53
|
+
const cosA = Math.cos(angle);
|
|
54
|
+
const sinA = Math.sin(angle);
|
|
55
|
+
const { width, height } = dimensions;
|
|
56
|
+
// Clear canvas with deep space translucent black
|
|
57
|
+
ctx.fillStyle = '#080808';
|
|
58
|
+
ctx.fillRect(0, 0, width, height);
|
|
59
|
+
// Draw subtle space grid or background stars
|
|
60
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.02)';
|
|
61
|
+
ctx.lineWidth = 1;
|
|
62
|
+
const step = 40;
|
|
63
|
+
for (let x = 0; x < width; x += step) {
|
|
64
|
+
ctx.beginPath();
|
|
65
|
+
ctx.moveTo(x, 0);
|
|
66
|
+
ctx.lineTo(x, height);
|
|
67
|
+
ctx.stroke();
|
|
68
|
+
}
|
|
69
|
+
for (let y = 0; y < height; y += step) {
|
|
70
|
+
ctx.beginPath();
|
|
71
|
+
ctx.moveTo(0, y);
|
|
72
|
+
ctx.lineTo(width, y);
|
|
73
|
+
ctx.stroke();
|
|
74
|
+
}
|
|
75
|
+
const cameraDistance = 2.5;
|
|
76
|
+
const scale = Math.min(width, height) * 0.42;
|
|
77
|
+
// 1. Project all nodes into 2D space
|
|
78
|
+
const projectedNodes = nodes.map((node) => {
|
|
79
|
+
// Rotate about Y axis
|
|
80
|
+
const rx = node.x * cosA - node.z * sinA;
|
|
81
|
+
const ry = node.y;
|
|
82
|
+
const rz = node.x * sinA + node.z * cosA;
|
|
83
|
+
// Perspective scaling factor
|
|
84
|
+
const depthScale = 1.0 / (cameraDistance - rz);
|
|
85
|
+
const projX = width / 2 + rx * scale * depthScale;
|
|
86
|
+
const projY = height / 2 + ry * scale * depthScale;
|
|
87
|
+
return Object.assign(Object.assign({}, node), { projX,
|
|
88
|
+
projY, projZ: rz, depthScale });
|
|
89
|
+
});
|
|
90
|
+
// 2. Sort nodes by depth (painter's algorithm: draw furthest first)
|
|
91
|
+
projectedNodes.sort((a, b) => a.projZ - b.projZ);
|
|
92
|
+
// Establish a lookup map for projected nodes
|
|
93
|
+
const projectedNodeMap = new Map();
|
|
94
|
+
for (const pNode of projectedNodes) {
|
|
95
|
+
projectedNodeMap.set(pNode.id, pNode);
|
|
96
|
+
}
|
|
97
|
+
// 3. Draw Cluster Nebulas (Group centers of gravity)
|
|
98
|
+
// Group nodes by cluster
|
|
99
|
+
const groups = Array.from(new Set(nodes.map(n => n.group)));
|
|
100
|
+
for (const group of groups) {
|
|
101
|
+
const groupNodes = projectedNodes.filter(n => n.group === group);
|
|
102
|
+
if (groupNodes.length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
// Calculate average projected position
|
|
105
|
+
let avgX = 0;
|
|
106
|
+
let avgY = 0;
|
|
107
|
+
let avgZ = 0;
|
|
108
|
+
for (const n of groupNodes) {
|
|
109
|
+
avgX += n.projX;
|
|
110
|
+
avgY += n.projY;
|
|
111
|
+
avgZ += n.projZ;
|
|
112
|
+
}
|
|
113
|
+
avgX /= groupNodes.length;
|
|
114
|
+
avgY /= groupNodes.length;
|
|
115
|
+
avgZ /= groupNodes.length;
|
|
116
|
+
// Base nebula color
|
|
117
|
+
const baseColor = GROUP_COLORS[group] || GROUP_COLORS.default;
|
|
118
|
+
// Nebula size scales with depth and cluster weight sum
|
|
119
|
+
const groupWeight = groupNodes.reduce((sum, n) => sum + n.weight, 0);
|
|
120
|
+
const depthScale = 1.0 / (cameraDistance - avgZ);
|
|
121
|
+
const radius = Math.max(30, groupWeight * 20 * depthScale);
|
|
122
|
+
// Simulate radial gradient with concentric transparent circles
|
|
123
|
+
// to avoid using the forbidden radial gradient method
|
|
124
|
+
const preservedAlpha = ctx.globalAlpha;
|
|
125
|
+
const iterations = 8;
|
|
126
|
+
for (let i = 0; i < iterations; i++) {
|
|
127
|
+
const ratio = (iterations - i) / iterations;
|
|
128
|
+
const currentRadius = radius * ratio;
|
|
129
|
+
ctx.beginPath();
|
|
130
|
+
ctx.arc(avgX, avgY, currentRadius, 0, Math.PI * 2);
|
|
131
|
+
ctx.fillStyle = baseColor;
|
|
132
|
+
// Soft exponential opacity falloff
|
|
133
|
+
ctx.globalAlpha = 0.015 * Math.pow(ratio, 2);
|
|
134
|
+
ctx.fill();
|
|
135
|
+
}
|
|
136
|
+
ctx.globalAlpha = preservedAlpha;
|
|
137
|
+
}
|
|
138
|
+
// 4. Draw Edges (Associations)
|
|
139
|
+
for (const edge of edges) {
|
|
140
|
+
const sourceNode = projectedNodeMap.get(edge.source);
|
|
141
|
+
const targetNode = projectedNodeMap.get(edge.target);
|
|
142
|
+
if (!sourceNode || !targetNode)
|
|
143
|
+
continue;
|
|
144
|
+
const averageDepthZ = (sourceNode.projZ + targetNode.projZ) / 2;
|
|
145
|
+
const depthFactor = (averageDepthZ + 1.0) / 2.0; // [0.0, 1.0] range
|
|
146
|
+
ctx.beginPath();
|
|
147
|
+
ctx.moveTo(sourceNode.projX, sourceNode.projY);
|
|
148
|
+
ctx.lineTo(targetNode.projX, targetNode.projY);
|
|
149
|
+
// Edge opacity maps to depth and strength
|
|
150
|
+
ctx.strokeStyle = '#333344';
|
|
151
|
+
ctx.globalAlpha = Math.max(0.05, 0.15 * edge.strength * depthFactor);
|
|
152
|
+
ctx.lineWidth = Math.max(0.5, 2.0 * edge.strength * depthFactor);
|
|
153
|
+
ctx.stroke();
|
|
154
|
+
ctx.globalAlpha = 1.0;
|
|
155
|
+
}
|
|
156
|
+
// 5. Draw Nodes & Labels
|
|
157
|
+
for (const pNode of projectedNodes) {
|
|
158
|
+
const isHovered = hoveredNodeId === pNode.id;
|
|
159
|
+
const isActive = activeNodeId === pNode.id;
|
|
160
|
+
const nodeColor = GROUP_COLORS[pNode.group] || GROUP_COLORS.default;
|
|
161
|
+
// Base particle radius based on depth and importance
|
|
162
|
+
const radius = Math.max(2, pNode.weight * 6 * pNode.depthScale);
|
|
163
|
+
// Highlight ring around active node
|
|
164
|
+
if (isActive) {
|
|
165
|
+
ctx.beginPath();
|
|
166
|
+
ctx.arc(pNode.projX, pNode.projY, radius * 2.2, 0, Math.PI * 2);
|
|
167
|
+
ctx.strokeStyle = nodeColor;
|
|
168
|
+
ctx.lineWidth = 1.5;
|
|
169
|
+
ctx.globalAlpha = 0.4 + 0.2 * Math.sin(Date.now() / 150);
|
|
170
|
+
ctx.stroke();
|
|
171
|
+
ctx.globalAlpha = 1.0;
|
|
172
|
+
}
|
|
173
|
+
// Draw core node particle
|
|
174
|
+
ctx.beginPath();
|
|
175
|
+
ctx.arc(pNode.projX, pNode.projY, radius, 0, Math.PI * 2);
|
|
176
|
+
ctx.fillStyle = nodeColor;
|
|
177
|
+
// Add brightness for hovered or active nodes
|
|
178
|
+
if (isHovered || isActive) {
|
|
179
|
+
ctx.shadowBlur = 12;
|
|
180
|
+
ctx.shadowColor = nodeColor;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
ctx.shadowBlur = 0;
|
|
184
|
+
}
|
|
185
|
+
ctx.fill();
|
|
186
|
+
ctx.shadowBlur = 0;
|
|
187
|
+
// Draw label text
|
|
188
|
+
const shouldShowLabel = isHovered || isActive || pNode.weight > 0.8;
|
|
189
|
+
if (shouldShowLabel) {
|
|
190
|
+
ctx.fillStyle = isHovered ? '#ffffff' : 'rgba(255, 255, 255, 0.7)';
|
|
191
|
+
ctx.font = `${Math.max(9, Math.round(11 * pNode.depthScale))}px monospace`;
|
|
192
|
+
ctx.textAlign = 'left';
|
|
193
|
+
ctx.textBaseline = 'middle';
|
|
194
|
+
const textX = pNode.projX + radius + 6;
|
|
195
|
+
const textY = pNode.projY;
|
|
196
|
+
ctx.fillText(pNode.label, textX, textY);
|
|
197
|
+
// Draw a small helper indicator for active/hovered node
|
|
198
|
+
if (isHovered) {
|
|
199
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
200
|
+
ctx.lineWidth = 0.5;
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
ctx.moveTo(pNode.projX, pNode.projY);
|
|
203
|
+
ctx.lineTo(textX - 2, textY);
|
|
204
|
+
ctx.stroke();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
animationFrameIdRef.current = requestAnimationFrame(renderLoop);
|
|
209
|
+
};
|
|
210
|
+
renderLoop();
|
|
211
|
+
return () => {
|
|
212
|
+
isComponentActive = false;
|
|
213
|
+
if (animationFrameIdRef.current) {
|
|
214
|
+
cancelAnimationFrame(animationFrameIdRef.current);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}, [nodes, edges, activeNodeId, hoveredNodeId, dimensions]);
|
|
218
|
+
// Spatial mouse verification to identify hovered node
|
|
219
|
+
const handleMouseMove = (event) => {
|
|
220
|
+
const canvas = canvasRef.current;
|
|
221
|
+
if (!canvas)
|
|
222
|
+
return;
|
|
223
|
+
const rect = canvas.getBoundingClientRect();
|
|
224
|
+
const mouseX = event.clientX - rect.left;
|
|
225
|
+
const mouseY = event.clientY - rect.top;
|
|
226
|
+
const { width, height } = dimensions;
|
|
227
|
+
const cameraDistance = 2.5;
|
|
228
|
+
const scale = Math.min(width, height) * 0.42;
|
|
229
|
+
const angle = angleRef.current;
|
|
230
|
+
const cosA = Math.cos(angle);
|
|
231
|
+
const sinA = Math.sin(angle);
|
|
232
|
+
let closestNodeId = undefined;
|
|
233
|
+
let minDistance = 16.0; // Click proximity tolerance threshold in pixels
|
|
234
|
+
for (const node of nodes) {
|
|
235
|
+
// Apply exact projection to find projected coordinates
|
|
236
|
+
const rx = node.x * cosA - node.z * sinA;
|
|
237
|
+
const ry = node.y;
|
|
238
|
+
const rz = node.x * sinA + node.z * cosA;
|
|
239
|
+
const depthScale = 1.0 / (cameraDistance - rz);
|
|
240
|
+
const projX = width / 2 + rx * scale * depthScale;
|
|
241
|
+
const projY = height / 2 + ry * scale * depthScale;
|
|
242
|
+
const dist = Math.hypot(projX - mouseX, projY - mouseY);
|
|
243
|
+
if (dist < minDistance) {
|
|
244
|
+
minDistance = dist;
|
|
245
|
+
closestNodeId = node.id;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (closestNodeId !== hoveredNodeId) {
|
|
249
|
+
setHoveredNodeId(closestNodeId);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
// Proximity click handler
|
|
253
|
+
const handleCanvasClick = () => {
|
|
254
|
+
if (hoveredNodeId && onSelectNode) {
|
|
255
|
+
onSelectNode(hoveredNodeId);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
return (React.createElement(GlassBox, { className: cn('relative flex flex-col min-h-[300px]', className), variant: "black" },
|
|
259
|
+
React.createElement("div", { className: "flex justify-between items-center border-b border-gray-800 pb-2 mb-2 font-mono" },
|
|
260
|
+
React.createElement("div", null,
|
|
261
|
+
React.createElement("h3", { className: "text-xs font-semibold text-white tracking-widest uppercase" }, "Belief Constellation Map"),
|
|
262
|
+
React.createElement("span", { className: "text-[9px] text-gray-500 uppercase" }, "High-Dimensional Concept Topology")),
|
|
263
|
+
activeNodeId && (React.createElement("div", { className: "text-[9px] text-[var(--cr-accent-cyan)] font-mono uppercase bg-emerald-950/40 border border-emerald-900/60 px-2 py-0.5 rounded" }, "Target Focus Active"))),
|
|
264
|
+
React.createElement("div", { ref: containerRef, className: "relative flex-grow w-full h-full overflow-hidden bg-black/40 rounded border border-gray-900" },
|
|
265
|
+
React.createElement("canvas", { ref: canvasRef, width: dimensions.width, height: dimensions.height, onMouseMove: handleMouseMove, onClick: handleCanvasClick, className: "block cursor-crosshair" }),
|
|
266
|
+
React.createElement("div", { className: "absolute bottom-2 left-2 pointer-events-none font-mono text-[8px] text-gray-600 uppercase flex flex-col space-y-0.5" },
|
|
267
|
+
React.createElement("span", null, "Projection: Perspective (Y-Rot)"),
|
|
268
|
+
React.createElement("span", null,
|
|
269
|
+
"Nodes: ",
|
|
270
|
+
nodes.length,
|
|
271
|
+
" | Edges: ",
|
|
272
|
+
edges.length)))));
|
|
273
|
+
};
|
|
@@ -9,8 +9,11 @@ export interface TemporalScrubberProps {
|
|
|
9
9
|
minRange: number;
|
|
10
10
|
maxRange: number;
|
|
11
11
|
forks?: TemporalFork[];
|
|
12
|
+
activeForkId?: string;
|
|
12
13
|
onScrub: (newTime: number) => void;
|
|
13
14
|
onForkSelect?: (forkId: string) => void;
|
|
15
|
+
onForkInitiate?: (branchPoint: number) => void;
|
|
16
|
+
onForkClose?: (forkId: string) => void;
|
|
14
17
|
className?: string;
|
|
15
18
|
}
|
|
16
19
|
export declare const TemporalScrubber: React.FC<TemporalScrubberProps>;
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { motion } from 'framer-motion';
|
|
12
12
|
import { cn } from './GlassBox';
|
|
13
|
-
export const TemporalScrubber = ({ timestamp, minRange, maxRange, forks = [], onScrub, onForkSelect, className }) => {
|
|
13
|
+
export const TemporalScrubber = ({ timestamp, minRange, maxRange, forks = [], activeForkId, onScrub, onForkSelect, onForkInitiate, onForkClose, className }) => {
|
|
14
14
|
const getPercentage = (val) => ((val - minRange) / (maxRange - minRange)) * 100;
|
|
15
15
|
return (React.createElement("div", { className: cn('flex flex-col items-stretch gap-4 p-4 bg-[var(--cr-glass-box-black)] rounded-xl border border-gray-800', className) },
|
|
16
16
|
React.createElement("div", { className: "flex justify-between items-center mb-2" },
|
|
17
|
-
React.createElement("
|
|
17
|
+
React.createElement("div", { className: "flex items-center gap-3" },
|
|
18
|
+
React.createElement("span", { className: "text-white uppercase tracking-widest text-xs font-bold" }, "Temporal Navigation"),
|
|
19
|
+
onForkInitiate && (React.createElement("button", { onClick: () => onForkInitiate(timestamp), className: "px-2 py-0.5 bg-purple-950/60 border border-purple-500/50 hover:bg-purple-900/60 text-purple-300 text-[10px] font-mono rounded transition-all uppercase tracking-wider font-bold" }, "Fork Timeline"))),
|
|
18
20
|
React.createElement("span", { className: "text-[var(--cr-accent-cyan)] font-mono text-xs" },
|
|
19
21
|
"T - ",
|
|
20
22
|
Math.floor((Date.now() - timestamp) / 1000),
|
|
@@ -25,11 +27,21 @@ export const TemporalScrubber = ({ timestamp, minRange, maxRange, forks = [], on
|
|
|
25
27
|
forks.map((fork, idx) => {
|
|
26
28
|
const startPct = getPercentage(fork.branchPoint);
|
|
27
29
|
const topOffset = idx % 2 === 0 ? 0 : 16;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
30
|
+
const isActive = activeForkId === fork.id;
|
|
31
|
+
return (React.createElement(motion.div, { key: fork.id, initial: { opacity: 0 }, animate: { opacity: 1 }, className: cn("absolute h-1 cursor-pointer group transition-all duration-300", isActive
|
|
32
|
+
? "bg-purple-500 shadow-[0_0_8px_rgba(168,85,247,0.8)] z-20"
|
|
33
|
+
: "bg-[var(--cr-accent-orange)] z-10"), style: { left: `${startPct}%`, top: topOffset, width: `calc(100% - ${startPct}%)` }, onClick: () => onForkSelect === null || onForkSelect === void 0 ? void 0 : onForkSelect(fork.id) },
|
|
34
|
+
React.createElement("div", { className: cn("absolute -left-1 -top-1 w-3 h-3 rounded-full transition-all duration-300", isActive ? "bg-purple-400 scale-125" : "bg-[var(--cr-accent-orange)]") }),
|
|
35
|
+
React.createElement("div", { className: cn("absolute -top-6 left-0 transition-opacity bg-black text-[10px] font-mono px-2 py-1 rounded flex items-center gap-1.5 whitespace-nowrap", isActive
|
|
36
|
+
? "opacity-100 text-purple-300 border border-purple-500/30"
|
|
37
|
+
: "opacity-0 group-hover:opacity-100 text-[var(--cr-accent-orange)]") },
|
|
38
|
+
React.createElement("span", null,
|
|
39
|
+
"\u2443 ",
|
|
40
|
+
fork.name),
|
|
41
|
+
onForkClose && (React.createElement("button", { onClick: (e) => {
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
onForkClose(fork.id);
|
|
44
|
+
}, className: "hover:text-white text-xs ml-1 focus:outline-none font-bold", title: "Dismiss Fork" }, "\u00D7")))));
|
|
33
45
|
}),
|
|
34
46
|
React.createElement("input", { type: "range", min: minRange, max: maxRange, value: timestamp, onChange: (e) => onScrub(Number(e.target.value)), className: "absolute top-[26px] w-full cursor-ew-resize opacity-0 z-10" }),
|
|
35
47
|
React.createElement(motion.div, { className: "absolute top-6 w-5 h-5 rounded-full bg-white border-2 border-[var(--cr-accent-cyan)] shadow-[0_0_10px_var(--cr-accent-cyan)] pointer-events-none", style: { left: `calc(${getPercentage(timestamp)}% - 10px)` }, layout: true }))));
|
package/dist/index.d.ts
CHANGED
|
@@ -23,3 +23,9 @@ export { EpistemicDeficitRadar } from './components/EpistemicDeficitRadar';
|
|
|
23
23
|
export type { EpistemicDeficitRadarProps } from './components/EpistemicDeficitRadar';
|
|
24
24
|
export { WasmSandboxCagingHud } from './components/WasmSandboxCagingHud';
|
|
25
25
|
export type { WasmSandboxCagingHudProps, SandboxHardwareTelemetry, AttestationReceipt } from './components/WasmSandboxCagingHud';
|
|
26
|
+
export { KeysigningCeremonyModal } from './components/KeysigningCeremonyModal';
|
|
27
|
+
export type { KeysigningCeremonyModalProps, SignerProfile, DelegatedAttestationReceipt } from './components/KeysigningCeremonyModal';
|
|
28
|
+
export { DlpParticleShield } from './components/DlpParticleShield';
|
|
29
|
+
export type { DlpParticleShieldProps } from './components/DlpParticleShield';
|
|
30
|
+
export { SemanticConstellationMap } from './components/SemanticConstellationMap';
|
|
31
|
+
export type { SemanticConstellationMapProps, SemanticConceptNode, SemanticConceptEdge } from './components/SemanticConstellationMap';
|
package/dist/index.js
CHANGED
|
@@ -29,3 +29,6 @@ export { DynamicToposRenderer } from './components/DynamicToposRenderer';
|
|
|
29
29
|
export { SpeculativeTreeRenderer } from './components/SpeculativeTreeRenderer';
|
|
30
30
|
export { EpistemicDeficitRadar } from './components/EpistemicDeficitRadar';
|
|
31
31
|
export { WasmSandboxCagingHud } from './components/WasmSandboxCagingHud';
|
|
32
|
+
export { KeysigningCeremonyModal } from './components/KeysigningCeremonyModal';
|
|
33
|
+
export { DlpParticleShield } from './components/DlpParticleShield';
|
|
34
|
+
export { SemanticConstellationMap } from './components/SemanticConstellationMap';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreason-ai/sensory-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Topos-Theoretic UI primitive library for the CoReason Cybernetic Manifold. Stateless, hollow React components that project coreason-manifest Pydantic ASTs into the DOM.",
|
|
5
5
|
"license": "Prosperity-3.0",
|
|
6
6
|
"author": "CoReason, Inc. <info@coreason.ai>",
|