Merge branch 'main' into pr-339

# Conflicts:
#	frontend/AGENTS.md
This commit is contained in:
Jack Kingsman
2026-07-25 15:19:51 -07:00
12 changed files with 940 additions and 34 deletions
@@ -13,12 +13,76 @@ import {
} from 'recharts';
import { Separator } from '../ui/separator';
import { api } from '../../api';
import type { StatisticsResponse } from '../../types';
import type { RegionScopeStats, StatisticsResponse } from '../../types';
function formatPercent(value: number): string {
return `${value.toFixed(1)}%`;
}
/**
* Regional flood-scope adoption. Deliberately shows fractions rather than bare
* percentages: with adoption this sparse, "3 of 117" communicates the sample
* size that "2.6%" hides.
*/
function RegionScopeStatsPanel({ stats }: { stats: RegionScopeStats }) {
// Corrupt RF captures land in the packet table with random headers, some of
// which claim to be region-scoped. At or below the measured floor there is
// nothing to report but noise, so withhold the percentage and say so.
const floor = stats.false_positive_floor;
const withinNoise = stats.scoped_messages <= floor;
// Real-world scoping is currently rare enough that the share rounds to "0.0%",
// which reads as a broken widget. Below that resolution the fraction alone is
// the honest presentation.
const showTrafficPct = stats.total_messages > 0 && !withinNoise && stats.scoped_pct >= 0.05;
return (
<div>
<h3 className="text-base font-semibold tracking-tight mb-2">Region Scope (24h)</h3>
<p className="text-[0.8125rem] text-muted-foreground mb-3">
How much local traffic uses regional flood scoping. Traffic covers all channel messages
heard, including channels you have no key for; senders only counts channels you can decrypt,
so the two use different denominators and will not match.
</p>
<div className="space-y-2">
<div className="flex justify-between items-center gap-4">
<span className="text-sm text-muted-foreground">Scoped messages</span>
<span className="font-medium text-right">
{stats.scoped_messages.toLocaleString()} of {stats.total_messages.toLocaleString()}
{showTrafficPct && (
<span className="text-muted-foreground"> ({formatPercent(stats.scoped_pct)})</span>
)}
</span>
</div>
<div className="flex justify-between items-center gap-4">
<span className="text-sm text-muted-foreground">Senders using regions</span>
<span className="font-medium text-right">
{stats.scoped_senders.toLocaleString()} of {stats.total_senders.toLocaleString()}
{stats.total_senders > 0 && (
<span className="text-muted-foreground">
{' '}
({formatPercent(stats.scoped_senders_pct)})
</span>
)}
</span>
</div>
</div>
{floor > 0 && stats.scoped_messages > 0 && (
<p className="text-[0.8125rem] text-muted-foreground mt-2">
{withinNoise
? `Scoped message count is at or below the estimated false-positive floor (${floor.toFixed(0)}) from corrupt packet captures, so it is not evidence of regional adoption.`
: `Includes an estimated ${floor.toFixed(0)} false positives from corrupt packet captures.`}{' '}
The sender count is unaffected it requires successful decryption.
</p>
)}
{stats.total_messages === 0 && (
<p className="text-sm text-muted-foreground mt-2">
No channel messages heard in the last 24 hours.
</p>
)}
</div>
);
}
const CHANNEL_BAR_COLORS = ['#0ea5e9', '#10b981', '#f59e0b', '#f43f5e', '#8b5cf6'];
const TOOLTIP_STYLE = {
@@ -404,6 +468,11 @@ export function SettingsStatisticsSection({ className }: { className?: string })
)}
</div>
<Separator />
{/* Region Scope */}
<RegionScopeStatsPanel stats={stats.region_scope_24h} />
{/* Busiest Channels */}
{stats.busiest_channels_24h.length > 0 && (
<>
+158
View File
@@ -777,6 +777,15 @@ describe('SettingsModal', () => {
double_byte_pct: 30,
triple_byte_pct: 20,
},
region_scope_24h: {
total_messages: 120,
scoped_messages: 40,
scoped_pct: 33.3,
false_positive_floor: 2,
total_senders: 12,
scoped_senders: 3,
scoped_senders_pct: 25,
},
packets_per_hour_72h: [
{ timestamp: 1711792800, count: 12 },
{ timestamp: 1711796400, count: 8 },
@@ -824,6 +833,146 @@ describe('SettingsModal', () => {
expect(screen.getByText('Known-channels active')).toBeInTheDocument();
expect(screen.getByText('Busiest Channels (24h)')).toBeInTheDocument();
expect(screen.getByText('Noise Floor (24h)')).toBeInTheDocument();
expect(screen.getByText('Region Scope (24h)')).toBeInTheDocument();
// Fractions, not bare percentages — the sample size matters at this sparsity
expect(screen.getByText(/40 of 120/)).toBeInTheDocument();
expect(screen.getByText(/3 of 12/)).toBeInTheDocument();
// 40 scoped is well above the floor of 2, so the percentage is shown
expect(screen.getByText(/33\.3%/)).toBeInTheDocument();
expect(
screen.queryByText(/at or below the estimated false-positive floor/)
).not.toBeInTheDocument();
});
it('discloses the false-positive floor and withholds a sub-0.1% scoped share', async () => {
const mockStats: StatisticsResponse = {
busiest_channels_24h: [],
contact_count: 0,
repeater_count: 0,
channel_count: 0,
total_packets: 0,
decrypted_packets: 0,
undecrypted_packets: 0,
total_dms: 0,
total_channel_messages: 0,
total_outgoing: 0,
contacts_heard: { last_hour: 0, last_24_hours: 0, last_week: 0 },
repeaters_heard: { last_hour: 0, last_24_hours: 0, last_week: 0 },
known_channels_active: { last_hour: 0, last_24_hours: 0, last_week: 0 },
path_hash_width_24h: {
total_packets: 0,
single_byte: 0,
double_byte: 0,
triple_byte: 0,
single_byte_pct: 0,
double_byte_pct: 0,
triple_byte_pct: 0,
},
// Mirrors real-world data: 70 "scoped" packets against a measured floor of
// 60 is corrupt-capture noise, not adoption.
region_scope_24h: {
total_messages: 391757,
scoped_messages: 70,
scoped_pct: 0.0179,
false_positive_floor: 60.3,
total_senders: 117,
scoped_senders: 3,
scoped_senders_pct: 2.56,
},
packets_per_hour_72h: [],
noise_floor_24h: {
sample_interval_seconds: 60,
coverage_seconds: 0,
latest_noise_floor_dbm: null,
latest_timestamp: null,
samples: [],
},
};
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify(mockStats), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
);
renderModal({ externalSidebarNav: true, desktopSection: 'statistics' });
await waitFor(() => {
expect(screen.getByText('Region Scope (24h)')).toBeInTheDocument();
});
// 70 scoped sits just above the 60.3 floor, so most of it is corrupt captures
expect(screen.getByText(/Includes an estimated 60 false positives/)).toBeInTheDocument();
// 0.0179% would render as a meaningless "0.0%", so the share is withheld
expect(screen.queryByText(/0\.0%/)).not.toBeInTheDocument();
expect(screen.getByText(/70 of 391,757/)).toBeInTheDocument();
// ...but the decryption-backed sender figure still stands
expect(screen.getByText(/3 of 117/)).toBeInTheDocument();
expect(screen.getByText(/2\.6%/)).toBeInTheDocument();
});
it('reports scoped traffic as noise when it is at or below the floor', async () => {
const mockStats: StatisticsResponse = {
busiest_channels_24h: [],
contact_count: 0,
repeater_count: 0,
channel_count: 0,
total_packets: 0,
decrypted_packets: 0,
undecrypted_packets: 0,
total_dms: 0,
total_channel_messages: 0,
total_outgoing: 0,
contacts_heard: { last_hour: 0, last_24_hours: 0, last_week: 0 },
repeaters_heard: { last_hour: 0, last_24_hours: 0, last_week: 0 },
known_channels_active: { last_hour: 0, last_24_hours: 0, last_week: 0 },
path_hash_width_24h: {
total_packets: 0,
single_byte: 0,
double_byte: 0,
triple_byte: 0,
single_byte_pct: 0,
double_byte_pct: 0,
triple_byte_pct: 0,
},
region_scope_24h: {
total_messages: 5000,
scoped_messages: 12,
scoped_pct: 0.24,
false_positive_floor: 20,
total_senders: 40,
scoped_senders: 0,
scoped_senders_pct: 0,
},
packets_per_hour_72h: [],
noise_floor_24h: {
sample_interval_seconds: 60,
coverage_seconds: 0,
latest_noise_floor_dbm: null,
latest_timestamp: null,
samples: [],
},
};
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify(mockStats), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
);
renderModal({ externalSidebarNav: true, desktopSection: 'statistics' });
await waitFor(() => {
expect(screen.getByText('Region Scope (24h)')).toBeInTheDocument();
});
expect(
screen.getByText(/at or below the estimated false-positive floor \(20\)/)
).toBeInTheDocument();
// Percentage withheld even though 0.24% would round visibly — it is noise
expect(screen.queryByText(/0\.2%/)).not.toBeInTheDocument();
});
it('fetches statistics when expanded in mobile external-nav mode', async () => {
@@ -850,6 +999,15 @@ describe('SettingsModal', () => {
double_byte_pct: 30,
triple_byte_pct: 20,
},
region_scope_24h: {
total_messages: 0,
scoped_messages: 0,
scoped_pct: 0,
false_positive_floor: 0,
total_senders: 0,
scoped_senders: 0,
scoped_senders_pct: 0,
},
packets_per_hour_72h: [],
noise_floor_24h: {
sample_interval_seconds: 60,
+19
View File
@@ -674,6 +674,24 @@ interface PacketsPerHourBucket {
count: number;
}
/**
* Regional flood-scope adoption over the last 24h. Two views with different
* denominators that will not agree — traffic spans all channels including
* undecryptable ones (so it carries a false-positive floor from corrupt RF
* captures), while senders requires decryption and is therefore noise-free but
* limited to channels we hold keys for.
*/
export interface RegionScopeStats {
total_messages: number;
scoped_messages: number;
scoped_pct: number;
/** Estimated false positives in scoped_messages. At or below this = not adoption. */
false_positive_floor: number;
total_senders: number;
scoped_senders: number;
scoped_senders_pct: number;
}
export interface StatisticsResponse {
busiest_channels_24h: BusyChannel[];
contact_count: number;
@@ -697,6 +715,7 @@ export interface StatisticsResponse {
double_byte_pct: number;
triple_byte_pct: number;
};
region_scope_24h: RegionScopeStats;
packets_per_hour_72h: PacketsPerHourBucket[];
noise_floor_24h: NoiseFloorHistoryStats;
}