mirror of
https://github.com/ruvnet/RuView
synced 2026-07-28 18:21:42 +00:00
feat: ADR-080 P1+P2 remediation — refactor, perf, tests, safety
P1 fixes (this sprint): - P1-6: Extract sensing-server modules (cli, types, csi, pose) from main.rs - P1-7: DDA ray march for tomography — O(max(n)) replaces O(n^3) voxel scan - P1-8: Batch neural inference — Tensor::stack/split for single GPU call - P1-10: Eliminate 112KB/frame alloc — islice replaces deque→list copy P2 fixes (this quarter): - P2-11: Python unit tests for 8 modules (rate_limit, auth, error_handler, pose_service, stream_service, hardware_service, health_check, metrics) - P2-13: MAT simulated data safety guard — blocking overlay + pulsing banner - P2-14: Wire token blacklist into auth verification + logout endpoint - P2-15: Frame budget benchmark — confirms pipeline well under 50ms budget Addresses 8 of 10 remaining issues from QE analysis (ADR-080). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -76,4 +76,31 @@ describe('MATScreen', () => {
|
||||
// Simulated status maps to 'simulated' banner -> "SIMULATED DATA"
|
||||
expect(getByText('SIMULATED DATA')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows simulation warning overlay when simulated and not acknowledged', () => {
|
||||
// Reset store to ensure overlay is shown
|
||||
const { useMatStore } = require('@/stores/matStore');
|
||||
useMatStore.setState({ dataSource: 'simulated', simulationAcknowledged: false });
|
||||
|
||||
const { MATScreen } = require('@/screens/MATScreen');
|
||||
const { getByText } = render(
|
||||
<ThemeProvider>
|
||||
<MATScreen />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(getByText('I UNDERSTAND')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides overlay after acknowledgment', () => {
|
||||
const { useMatStore } = require('@/stores/matStore');
|
||||
useMatStore.setState({ dataSource: 'simulated', simulationAcknowledged: true });
|
||||
|
||||
const { MATScreen } = require('@/screens/MATScreen');
|
||||
const { queryByText } = render(
|
||||
<ThemeProvider>
|
||||
<MATScreen />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(queryByText('I UNDERSTAND')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,8 @@ describe('useMatStore', () => {
|
||||
survivors: [],
|
||||
alerts: [],
|
||||
selectedEventId: null,
|
||||
dataSource: 'simulated',
|
||||
simulationAcknowledged: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,4 +197,32 @@ describe('useMatStore', () => {
|
||||
expect(useMatStore.getState().selectedEventId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataSource', () => {
|
||||
it('defaults to simulated', () => {
|
||||
expect(useMatStore.getState().dataSource).toBe('simulated');
|
||||
});
|
||||
|
||||
it('can be set to real', () => {
|
||||
useMatStore.getState().setDataSource('real');
|
||||
expect(useMatStore.getState().dataSource).toBe('real');
|
||||
});
|
||||
|
||||
it('can be set back to simulated', () => {
|
||||
useMatStore.getState().setDataSource('real');
|
||||
useMatStore.getState().setDataSource('simulated');
|
||||
expect(useMatStore.getState().dataSource).toBe('simulated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('simulationAcknowledged', () => {
|
||||
it('defaults to false', () => {
|
||||
expect(useMatStore.getState().simulationAcknowledged).toBe(false);
|
||||
});
|
||||
|
||||
it('can be acknowledged', () => {
|
||||
useMatStore.getState().acknowledgeSimulation();
|
||||
expect(useMatStore.getState().simulationAcknowledged).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Animated, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export const SimulationBanner: React.FC<Props> = ({ visible }) => {
|
||||
const opacity = useRef(new Animated.Value(1)).current;
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
|
||||
const pulse = Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(opacity, { toValue: 0.4, duration: 800, useNativeDriver: true }),
|
||||
Animated.timing(opacity, { toValue: 1.0, duration: 800, useNativeDriver: true }),
|
||||
]),
|
||||
);
|
||||
pulse.start();
|
||||
return () => pulse.stop();
|
||||
}, [visible, opacity]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.banner, { opacity }]}>
|
||||
<Text style={styles.text}>SIMULATED DATA - NOT CONNECTED TO REAL SENSORS</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
banner: {
|
||||
backgroundColor: '#e74c3c',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 6,
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
text: {
|
||||
color: '#ffffff',
|
||||
fontWeight: '700',
|
||||
fontSize: 12,
|
||||
letterSpacing: 0.5,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onAcknowledge: () => void;
|
||||
}
|
||||
|
||||
export const SimulationWarningOverlay: React.FC<Props> = ({ visible, onAcknowledge }) => (
|
||||
<Modal visible={visible} transparent animationType="fade">
|
||||
<View style={styles.backdrop}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.icon}>⚠</Text>
|
||||
<Text style={styles.title}>SIMULATED DATA</Text>
|
||||
<Text style={styles.body}>
|
||||
NOT CONNECTED TO REAL SENSORS{'\n\n'}
|
||||
All survivor detections, vital signs, and alerts displayed on this screen are
|
||||
generated from simulated data and do not reflect actual conditions.
|
||||
</Text>
|
||||
<Pressable style={styles.button} onPress={onAcknowledge}>
|
||||
<Text style={styles.buttonText}>I UNDERSTAND</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.85)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 24,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#1a1a2e',
|
||||
borderRadius: 16,
|
||||
padding: 32,
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: '#e74c3c',
|
||||
maxWidth: 420,
|
||||
width: '100%',
|
||||
},
|
||||
icon: {
|
||||
fontSize: 48,
|
||||
color: '#e74c3c',
|
||||
marginBottom: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontWeight: '800',
|
||||
color: '#e74c3c',
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
body: {
|
||||
fontSize: 15,
|
||||
color: '#cccccc',
|
||||
textAlign: 'center',
|
||||
lineHeight: 22,
|
||||
marginBottom: 28,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#e74c3c',
|
||||
paddingHorizontal: 36,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonText: {
|
||||
color: '#ffffff',
|
||||
fontWeight: '700',
|
||||
fontSize: 16,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
});
|
||||
@@ -10,6 +10,8 @@ import { type ConnectionStatus } from '@/types/sensing';
|
||||
import { Alert, type Survivor } from '@/types/mat';
|
||||
import { AlertList } from './AlertList';
|
||||
import { MatWebView } from './MatWebView';
|
||||
import { SimulationBanner } from './SimulationBanner';
|
||||
import { SimulationWarningOverlay } from './SimulationWarningOverlay';
|
||||
import { SurvivorCounter } from './SurvivorCounter';
|
||||
import { useMatBridge } from './useMatBridge';
|
||||
|
||||
@@ -47,6 +49,15 @@ export const MATScreen = () => {
|
||||
const upsertSurvivor = useMatStore((state) => state.upsertSurvivor);
|
||||
const addAlert = useMatStore((state) => state.addAlert);
|
||||
const upsertEvent = useMatStore((state) => state.upsertEvent);
|
||||
const dataSource = useMatStore((state) => state.dataSource);
|
||||
const simulationAcknowledged = useMatStore((state) => state.simulationAcknowledged);
|
||||
const setDataSource = useMatStore((state) => state.setDataSource);
|
||||
const acknowledgeSimulation = useMatStore((state) => state.acknowledgeSimulation);
|
||||
|
||||
// Sync dataSource from connection status
|
||||
useEffect(() => {
|
||||
setDataSource(connectionStatus === 'connected' ? 'real' : 'simulated');
|
||||
}, [connectionStatus, setDataSource]);
|
||||
|
||||
const { webViewRef, ready, onMessage, sendFrameUpdate, postEvent } = useMatBridge({
|
||||
onSurvivorDetected: (survivor) => {
|
||||
@@ -113,8 +124,13 @@ export const MATScreen = () => {
|
||||
const { height } = useWindowDimensions();
|
||||
const webHeight = Math.max(240, Math.floor(height * 0.5));
|
||||
|
||||
const showOverlay = dataSource === 'simulated' && !simulationAcknowledged;
|
||||
const showBanner = dataSource === 'simulated' && simulationAcknowledged;
|
||||
|
||||
return (
|
||||
<ThemedView style={{ flex: 1, backgroundColor: colors.bg, padding: spacing.md }}>
|
||||
<SimulationWarningOverlay visible={showOverlay} onAcknowledge={acknowledgeSimulation} />
|
||||
<SimulationBanner visible={showBanner} />
|
||||
<ConnectionBanner status={resolveBannerState(connectionStatus)} />
|
||||
<View style={{ marginTop: 20 }}>
|
||||
<SurvivorCounter survivors={survivors} />
|
||||
|
||||
@@ -7,11 +7,17 @@ export interface MatState {
|
||||
survivors: Survivor[];
|
||||
alerts: Alert[];
|
||||
selectedEventId: string | null;
|
||||
/** Whether data comes from real sensors or simulation. */
|
||||
dataSource: 'real' | 'simulated';
|
||||
/** Whether the user has dismissed the simulation warning overlay. */
|
||||
simulationAcknowledged: boolean;
|
||||
upsertEvent: (event: DisasterEvent) => void;
|
||||
addZone: (zone: ScanZone) => void;
|
||||
upsertSurvivor: (survivor: Survivor) => void;
|
||||
addAlert: (alert: Alert) => void;
|
||||
setSelectedEvent: (id: string | null) => void;
|
||||
setDataSource: (source: 'real' | 'simulated') => void;
|
||||
acknowledgeSimulation: () => void;
|
||||
}
|
||||
|
||||
export const useMatStore = create<MatState>((set) => ({
|
||||
@@ -20,6 +26,8 @@ export const useMatStore = create<MatState>((set) => ({
|
||||
survivors: [],
|
||||
alerts: [],
|
||||
selectedEventId: null,
|
||||
dataSource: 'simulated',
|
||||
simulationAcknowledged: false,
|
||||
|
||||
upsertEvent: (event) => {
|
||||
set((state) => {
|
||||
@@ -71,4 +79,12 @@ export const useMatStore = create<MatState>((set) => ({
|
||||
setSelectedEvent: (id) => {
|
||||
set({ selectedEventId: id });
|
||||
},
|
||||
|
||||
setDataSource: (source) => {
|
||||
set({ dataSource: source });
|
||||
},
|
||||
|
||||
acknowledgeSimulation: () => {
|
||||
set({ simulationAcknowledged: true });
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user