feat(decnet_web/AttackerDetail): visual refresh of Behavioural Primitives panel

* Per-domain icons (Keyboard / Cpu / Clock / Activity / Globe / Sparkles).
* Domain headers use BEHAVIOUR_DOMAIN_LABELS with letter-spacing +
  primitive-count badge on the right.
* Bordered domain groups instead of flat list; aligned leaf / value /
  confidence columns with monospace value rendering.
* Section title: BEHAVIOURAL PRIMITIVES -> BEHAVE PRIMITIVES (matches
  the BEHAVE-SHELL extractor naming).
This commit is contained in:
2026-05-09 02:24:37 -04:00
parent 9cc3272a0d
commit 5de4b5e290

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { Activity, AlertTriangle, ArrowLeft, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Crosshair, Eye, Fingerprint, Globe, Shield, Clock, Wifi, Lock, FileKey, Radio, Timer, Paperclip, Terminal, Package, FileText, Mail, AtSign } from '../icons'; import { Activity, AlertTriangle, ArrowLeft, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Cpu, Crosshair, Eye, Fingerprint, Globe, Keyboard, Shield, Clock, Sparkles, Wifi, Lock, FileKey, Radio, Timer, Paperclip, Terminal, Package, FileText, Mail, AtSign } from '../icons';
import api from '../utils/api'; import api from '../utils/api';
import ArtifactDrawer from './ArtifactDrawer'; import ArtifactDrawer from './ArtifactDrawer';
import MailDrawer from './MailDrawer'; import MailDrawer from './MailDrawer';
@@ -912,6 +912,24 @@ const BEHAVIOUR_DOMAIN_ORDER: ReadonlyArray<string> = [
'environmental', 'emotional_valence', 'environmental', 'emotional_valence',
]; ];
const BEHAVIOUR_DOMAIN_LABELS: Record<string, string> = {
motor: 'MOTOR',
cognitive: 'COGNITIVE',
temporal: 'TEMPORAL',
operational: 'OPERATIONAL',
environmental: 'ENVIRONMENTAL',
emotional_valence: 'EMOTIONAL VALENCE',
};
const BEHAVIOUR_DOMAIN_ICONS: Record<string, React.ComponentType<{ size?: number; style?: React.CSSProperties }>> = {
motor: Keyboard,
cognitive: Cpu,
temporal: Clock,
operational: Activity,
environmental: Globe,
emotional_valence: Sparkles,
};
function _domainOf(primitive: string): string { function _domainOf(primitive: string): string {
return primitive.split('.', 1)[0]; return primitive.split('.', 1)[0];
} }
@@ -963,25 +981,81 @@ export const BehaviouralPrimitivesPanel: React.FC<{
...Array.from(groups.keys()).filter((d) => !BEHAVIOUR_DOMAIN_ORDER.includes(d)).sort(), ...Array.from(groups.keys()).filter((d) => !BEHAVIOUR_DOMAIN_ORDER.includes(d)).sort(),
]; ];
return ( return (
<div className="behaviour-panel" data-testid="behaviour-panel"> <div
{orderedDomains.map((domain) => ( className="behaviour-panel"
<div key={domain} className="behaviour-group" data-testid={`behaviour-group-${domain}`}> data-testid="behaviour-panel"
<div className="page-header dim">{domain.toUpperCase()}</div> style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}
{groups.get(domain)!.map((obs) => ( >
<div {orderedDomains.map((domain) => {
key={obs.primitive} const Icon = BEHAVIOUR_DOMAIN_ICONS[domain] ?? Activity;
className="behaviour-row" const label = BEHAVIOUR_DOMAIN_LABELS[domain] ?? domain.toUpperCase();
data-testid={`behaviour-row-${obs.primitive}`} const rows = groups.get(domain)!;
> return (
<span className="behaviour-leaf">{_leafOf(obs.primitive)}</span> <div
<span className="behaviour-value matrix-text">{_renderValue(obs.value)}</span> key={domain}
<span className="behaviour-confidence dim"> className="behaviour-group"
{(obs.confidence * 100).toFixed(0)}% data-testid={`behaviour-group-${domain}`}
style={{ border: '1px solid var(--border-color)', padding: '12px 16px' }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '10px' }}>
<Icon size={14} style={{ opacity: 0.6 }} />
<span style={{ fontSize: '0.75rem', letterSpacing: '2px', fontWeight: 'bold' }}>
{label}
</span>
<span className="dim" style={{ fontSize: '0.65rem', marginLeft: 'auto' }}>
{rows.length} {rows.length === 1 ? 'PRIMITIVE' : 'PRIMITIVES'}
</span> </span>
</div> </div>
))} <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
</div> {rows.map((obs) => (
))} <div
key={obs.primitive}
className="behaviour-row"
data-testid={`behaviour-row-${obs.primitive}`}
style={{ display: 'flex', gap: '12px', alignItems: 'baseline' }}
>
<span
className="behaviour-leaf dim"
style={{
fontSize: '0.7rem',
letterSpacing: '1px',
minWidth: '180px',
textTransform: 'uppercase',
}}
>
{_leafOf(obs.primitive)}
</span>
<span
className="behaviour-value matrix-text"
style={{
fontFamily: 'monospace',
fontSize: '0.85rem',
flex: 1,
wordBreak: 'break-word',
}}
>
{_renderValue(obs.value)}
</span>
<span
className="behaviour-confidence dim"
style={{
fontSize: '0.65rem',
fontFamily: 'monospace',
letterSpacing: '1px',
border: '1px solid var(--border-color)',
borderRadius: '2px',
padding: '1px 6px',
whiteSpace: 'nowrap',
}}
>
{(obs.confidence * 100).toFixed(0)}%
</span>
</div>
))}
</div>
</div>
);
})}
</div> </div>
); );
}; };
@@ -1894,7 +1968,7 @@ const AttackerDetail: React.FC = () => {
{/* Behavioural primitives (BEHAVE-SHELL) */} {/* Behavioural primitives (BEHAVE-SHELL) */}
<Section <Section
title="BEHAVIOURAL PRIMITIVES" title="BEHAVE PRIMITIVES"
open={openSections.behavioural} open={openSections.behavioural}
onToggle={() => toggle('behavioural')} onToggle={() => toggle('behavioural')}
> >