Skip to content

Commit bfa223f

Browse files
committed
simplizity
1 parent dcece94 commit bfa223f

22 files changed

Lines changed: 715 additions & 378 deletions

collector/src/database.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,7 +2794,7 @@ function inactivityFirstMissEvent(miss, network, observedAt) {
27942794
source: 'aztec_sentinel',
27952795
type: 'inactivity_first_miss',
27962796
severity: 'warning',
2797-
title: 'First missed duty observed',
2797+
title: 'Missed duty observed',
27982798
body: `${shortAddress(miss.sequencer)} missed a duty in epoch ${miss.epoch}; this is precursor evidence, not a registered slash offense.`,
27992799
data: {
28002800
certainty: 'pending',
@@ -2828,7 +2828,7 @@ function inactivityEpochEvent(performance, network, observedAt) {
28282828
title: thresholdReached
28292829
? 'Inactivity streak reached threshold'
28302830
: 'Inactive duty epoch completed',
2831-
body: `${shortAddress(performance.sequencer)} missed ${performance.missed}/${performance.total} observed duties in epoch ${performance.epoch}; duty-bearing inactive streak ${performance.inactiveStreak}/${performance.threshold}.`,
2831+
body: `${shortAddress(performance.sequencer)} missed ${performance.missed}/${performance.total} observed duties in epoch ${performance.epoch}; inactive streak ${performance.inactiveStreak}/${performance.threshold}.`,
28322832
data: {
28332833
certainty: 'pending',
28342834
sequencer: performance.sequencer,

collector/test/api-server.test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ test('public journal exposes Sentinel precursor feed and detail', async (t) => {
603603
source: 'aztec_sentinel',
604604
type: 'inactivity_first_miss',
605605
severity: 'warning',
606-
title: 'First missed duty observed',
606+
title: 'Missed duty observed',
607607
body: 'Precursor evidence, not a registered slash offense.',
608608
data: {
609609
certainty: 'pending',
@@ -670,7 +670,7 @@ test('sequencer record returns its indexed journal and exact protocol timing sna
670670
source: 'aztec_sentinel',
671671
type: 'inactivity_first_miss',
672672
severity: 'warning',
673-
title: 'First missed duty observed',
673+
title: 'Missed duty observed',
674674
body: 'Precursor only.',
675675
data: { certainty: 'pending', sequencer: SEQUENCER_A, epoch: '42', slot: '1344' },
676676
observedAt: NOW,

src/App.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ describe('top-level view isolation', () => {
3838
expect(markup).toContain('brutal-button--nav-selected');
3939
});
4040

41+
it('keeps the network feed collapsed while a sequencer record is selected', () => {
42+
installBrowser(
43+
'https://slashmon.example/?view=pingme&sequencer=0x1111111111111111111111111111111111111111',
44+
);
45+
46+
const markup = renderToStaticMarkup(<App />);
47+
48+
expect(scannerSpy).not.toHaveBeenCalled();
49+
expect(markup).toContain('<details');
50+
expect(markup).toContain('Network feed');
51+
expect(markup).toContain('id="sequencer-record-timeline"');
52+
});
53+
4154
it('treats removed and unknown views as the Monitor', () => {
4255
installBrowser('https://slashmon.example/?view=unknown&network=testnet');
4356

src/App.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,18 @@ export function App() {
7575
}, [isTestnet, restartScanner]);
7676

7777
const selectSequencer = useCallback((sequencer: Address | null) => {
78+
if (
79+
sequencer &&
80+
location.selectedSequencer?.toLowerCase() === sequencer.toLowerCase()
81+
) {
82+
document.getElementById('sequencer-record-timeline')
83+
?.scrollIntoView({ behavior: 'smooth', block: 'start' });
84+
return;
85+
}
7886
const next = urlForSequencer(window.location.href, sequencer);
7987
window.history.pushState({}, '', next);
8088
setLocation(parseAppSearch(next.search));
81-
window.scrollTo({ top: 0, behavior: 'smooth' });
82-
}, []);
89+
}, [location.selectedSequencer]);
8390

8491
const updateRpc = useCallback((url: string) => {
8592
const savedUrl = setRpcOverride(config.chainId, url);
@@ -114,8 +121,8 @@ export function App() {
114121
{location.view === 'pingme' ? (
115122
<main className="mx-auto max-w-7xl px-4 py-8">
116123
<BackendOverview
117-
key={`${location.network}:${location.selectedEventId ?? ''}:${location.selectedSequencer ?? ''}`}
118124
network={location.network}
125+
selectedEventId={location.selectedEventId}
119126
selectedSequencer={location.selectedSequencer}
120127
onSelectSequencer={selectSequencer}
121128
/>

src/components/BackendOverview.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,26 @@ import type { MonitorNetwork } from '@/types/backendApi';
88

99
export function BackendOverview({
1010
network,
11+
selectedEventId,
1112
selectedSequencer,
1213
onSelectSequencer,
1314
}: {
1415
network: MonitorNetwork;
16+
selectedEventId: string | null;
1517
selectedSequencer: Address | null;
1618
onSelectSequencer: (sequencer: Address | null) => void;
1719
}) {
18-
const monitor = useBackendMonitor(network);
20+
const monitor = useBackendMonitor(network, selectedEventId);
1921
const configuredNetwork = monitor.config?.network;
22+
const networkFeed = (
23+
<EventHistory
24+
events={monitor.events?.data ?? []}
25+
hasWatchlistCapability={monitor.hasWatchlistCapability}
26+
selectedEventId={selectedEventId}
27+
selectedEventError={monitor.selectedEventError}
28+
onSelectSequencer={onSelectSequencer}
29+
/>
30+
);
2031

2132
const health = (
2233
<SourceHealthBanner
@@ -57,12 +68,17 @@ export function BackendOverview({
5768
onSelectSequencer={onSelectSequencer}
5869
/>
5970
<div className="mb-10">
60-
<EventHistory
61-
events={monitor.events?.data ?? []}
62-
hasWatchlistCapability={monitor.hasWatchlistCapability}
63-
selectedEventError={monitor.selectedEventError}
64-
onSelectSequencer={onSelectSequencer}
65-
/>
71+
{selectedSequencer ? (
72+
<details className="border-5 border-aqua bg-lapis p-4 shadow-brutal-aqua">
73+
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 font-black text-aqua">
74+
<span className="text-lg">Network feed</span>
75+
<span className="border-3 border-brand-black bg-aqua px-2 py-1 text-xs uppercase text-brand-black">
76+
{monitor.events?.data.length ?? 0} events
77+
</span>
78+
</summary>
79+
<div className="mt-5">{networkFeed}</div>
80+
</details>
81+
) : networkFeed}
6682
</div>
6783
</>
6884
);

src/components/CopyButton.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ describe('CopyButton', () => {
1212
);
1313

1414
expect(markup).toContain('aria-label="Copy sequencer address"');
15+
expect(markup).toContain('text-chartreuse');
16+
expect(markup).toContain('bg-transparent');
17+
expect(markup).not.toContain('bg-whisper-white');
18+
expect(markup).not.toContain('shadow-');
1519
expect(markup).not.toContain('>Copy</span>');
1620
});
1721
});

src/components/CopyButton.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,21 @@ export function CopyButton({
4141
<button
4242
type="button"
4343
onClick={() => void copy()}
44-
className={`inline-flex h-9 w-9 shrink-0 items-center justify-center border-3 border-brand-black text-brand-black shadow-[3px_3px_0_var(--color-brand-black)] transition-transform hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none ${state === 'failed' ? 'bg-vermillion' : state === 'copied' ? 'bg-chartreuse' : 'bg-whisper-white'} ${className}`}
44+
className={`inline-flex h-7 w-7 shrink-0 items-center justify-center bg-transparent transition-colors hover:bg-chartreuse hover:text-brand-black focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-chartreuse ${state === 'failed' ? 'text-vermillion' : 'text-chartreuse'} ${className}`}
4545
title={statusLabel}
4646
aria-label={statusLabel}
4747
aria-live="polite"
4848
>
4949
{state === 'copied' ? (
50-
<svg className="h-4 w-4 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
50+
<svg className="h-5 w-5 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
5151
<path strokeLinecap="square" strokeLinejoin="miter" d="m5 12 4 4L19 6" />
5252
</svg>
5353
) : state === 'failed' ? (
54-
<svg className="h-4 w-4 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
54+
<svg className="h-5 w-5 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
5555
<path strokeLinecap="square" strokeLinejoin="miter" d="M6 6l12 12M18 6 6 18" />
5656
</svg>
5757
) : (
58-
<svg className="h-4 w-4 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
58+
<svg className="h-5 w-5 stroke-[3]" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
5959
<rect x="8" y="8" width="11" height="11" />
6060
<path strokeLinecap="square" d="M16 8V5H5v11h3" />
6161
</svg>

src/components/EventHistory.test.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,13 @@ describe('Pingme journal cards', () => {
8282
);
8383

8484
expect(markup).toContain('L1 · confirmed');
85-
expect(markup).toContain('Show all 4 targets');
86-
for (const target of targets) expect(markup).toContain(`Open ${target} on Dashtec`);
85+
expect(markup).toContain('Show 1 more target');
86+
expect(markup).not.toContain('Show all 4 targets');
87+
for (const target of targets) {
88+
const linkTitle = `title="Open ${target} on Dashtec"`;
89+
expect(markup).toContain(linkTitle);
90+
expect(markup.split(linkTitle)).toHaveLength(2);
91+
}
8792
expect(markup).toContain('L1 reason: not encoded · matched node evidence below');
8893
expect(markup).toContain('Node evidence: Inactivity · epoch 772');
8994
expect(markup).toContain('Active round 195');
@@ -133,7 +138,7 @@ describe('Pingme journal cards', () => {
133138
expect(markup).not.toContain('aztec_node');
134139
});
135140

136-
it('labels a precursor duty miss as the first missed slot', () => {
141+
it('labels a precursor duty miss without claiming it was first', () => {
137142
vi.stubGlobal('window', { location: new URL('https://slashmon.example/?view=pingme') });
138143
const event: MonitorEvent = {
139144
id: 'event-precursor',
@@ -165,7 +170,10 @@ describe('Pingme journal cards', () => {
165170
<EventHistory events={[event]} hasWatchlistCapability={false} />,
166171
);
167172

168-
expect(markup).toContain('First missed slot 26690');
173+
expect(markup).toContain('Missed slot 26690');
174+
expect(markup).toContain('Missed duty observed');
175+
expect(markup).not.toContain('First missed slot');
176+
expect(markup).not.toContain('First missed duty observed');
169177
expect(markup).not.toContain('Epoch starts at slot 26690');
170178
});
171179

src/components/EventHistory.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import { useEffect, useRef } from 'react';
2-
import { SequencerAddressLink } from './SequencerAddressLink';
2+
import { SequencerAddressControl } from './SequencerAddressControl';
3+
import { getEventTitle, getEventVisual } from '@/lib/presentation';
34
import type { EventL1Context, MonitorEvent } from '@/types/backendApi';
45

56
interface EventHistoryProps {
67
events: MonitorEvent[];
78
hasWatchlistCapability: boolean;
9+
selectedEventId?: string | null;
810
selectedEventError?: string | null;
911
onSelectSequencer?: (sequencer: MonitorEvent['targets'][number]) => void;
1012
}
1113

1214
export function EventHistory({
1315
events,
1416
hasWatchlistCapability,
17+
selectedEventId = null,
1518
selectedEventError = null,
1619
onSelectSequencer,
1720
}: EventHistoryProps) {
18-
const selectedEventId = new URLSearchParams(window.location.search).get('event');
1921
const scrolledEventIdRef = useRef<string | null>(null);
2022

2123
useEffect(() => {
@@ -36,7 +38,7 @@ export function EventHistory({
3638
<div className="mb-2 inline-flex border-3 border-brand-black bg-aqua px-3 py-1 text-xs font-black uppercase text-brand-black">
3739
Pingme Journal
3840
</div>
39-
<h2 className="text-2xl font-black text-aqua">Slashing Activity</h2>
41+
<h2 className="text-2xl font-black text-aqua">Network feed</h2>
4042
<p className="mt-2 text-sm font-bold text-whisper-white">
4143
{hasWatchlistCapability
4244
? 'Node signals and confirmed L1 activity for this browser’s watch list.'
@@ -86,17 +88,18 @@ function EventCard({
8688
onSelectSequencer?: EventHistoryProps['onSelectSequencer'];
8789
}) {
8890
const pending = event.certainty === 'pending';
91+
const visual = getEventVisual(event.type);
8992
return (
9093
<article
9194
id={`event-${event.id}`}
9295
className={`border-3 bg-brand-black p-4 ${
93-
selected ? 'border-chartreuse shadow-brutal-chartreuse' : pending ? 'border-orchid' : 'border-aqua'
96+
selected ? 'border-chartreuse shadow-brutal-chartreuse' : visual.border
9497
}`}
9598
>
9699
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
97100
<div className="min-w-0">
98-
<h3 className={`text-base font-black ${pending ? 'text-orchid' : 'text-aqua'}`}>
99-
{event.title}
101+
<h3 className={`text-base font-black ${visual.text}`}>
102+
{getEventTitle(event)}
100103
</h3>
101104
<EventTargets event={event} onSelectSequencer={onSelectSequencer} />
102105
</div>
@@ -127,7 +130,11 @@ function EventTargets({
127130
if (event.targets.length === 0) {
128131
return null;
129132
}
130-
const preview = event.targets.slice(0, 3);
133+
const uniqueTargets = event.targets.filter((target, index, targets) =>
134+
targets.findIndex((candidate) =>
135+
candidate.toLowerCase() === target.toLowerCase()) === index);
136+
const preview = uniqueTargets.slice(0, 3);
137+
const remaining = uniqueTargets.slice(3);
131138
return (
132139
<div className="mt-2">
133140
<div className="flex flex-wrap items-center gap-x-3 gap-y-1">
@@ -145,13 +152,13 @@ function EventTargets({
145152
);
146153
})}
147154
</div>
148-
{event.targets.length > 3 && (
155+
{remaining.length > 0 && (
149156
<details className="mt-2 text-xs text-whisper-white/80">
150157
<summary className="w-fit cursor-pointer font-black uppercase text-aqua underline underline-offset-4">
151-
Show all {event.targets.length} targets
158+
Show {remaining.length} more target{remaining.length === 1 ? '' : 's'}
152159
</summary>
153160
<div className="mt-2 grid gap-2 border-l-3 border-aqua/50 pl-3 sm:grid-cols-2 xl:grid-cols-3">
154-
{event.targets.map((target) => {
161+
{remaining.map((target) => {
155162
const amount = event.l1?.actions.find((action) =>
156163
action.sequencer.toLowerCase() === target.toLowerCase())?.amount;
157164
return (
@@ -189,21 +196,14 @@ function EventTarget({
189196
item.sequencer.toLowerCase() === target.toLowerCase());
190197
return (
191198
<div className="min-w-0">
192-
<SequencerAddressLink
199+
<SequencerAddressControl
193200
address={target}
194201
chars={full ? undefined : 6}
195202
full={full}
203+
showCopy
204+
onOpenRecord={onSelectSequencer}
196205
className="font-mono text-xs font-bold text-whisper-white"
197206
/>
198-
{onSelectSequencer && (
199-
<button
200-
type="button"
201-
onClick={() => onSelectSequencer(target)}
202-
className="mt-1 block text-[0.65rem] font-black uppercase text-orchid underline underline-offset-4 hover:text-chartreuse"
203-
>
204-
Open record
205-
</button>
206-
)}
207207
{amount && (
208208
<span className="text-[0.68rem] font-bold text-whisper-white/55">
209209
{formatAztec(amount)} AZTEC proposed
@@ -234,7 +234,7 @@ function EventFacts({ event }: { event: MonitorEvent }) {
234234
if (offense.epoch) facts.push(`Epoch ${offense.epoch}`);
235235
if (offense.slot) {
236236
const slotLabel = event.type === 'inactivity_first_miss'
237-
? 'First missed slot'
237+
? 'Missed slot'
238238
: offense.timeUnit === 'epoch'
239239
? 'Epoch starts at slot'
240240
: 'Slot';

0 commit comments

Comments
 (0)