🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
Dangerous Pi - Visualization & Guided Workflows Plan
Status: 🎯 In Planning Priority: High (Post-MVP Phase 1) Target Timeline: 4-6 weeks implementation Last Updated: 2025-11-26
Executive Summary
Dangerous Pi will implement data visualization and guided workflows using Victory Charts, a cross-platform charting library that works seamlessly across Web (Remix), React Native (mobile), and Electron (desktop) applications.
Key Decisions
- Charting Library: Victory (cross-platform, mobile-first)
- Bundle Impact: ~50KB (acceptable via code splitting)
- Primary Target: Mobile/smartphone users (📱 80% of usage)
- Architecture: Shared components across all platforms
1. Why Victory Charts?
Cross-Platform Requirements
| Platform | Framework | Victory Package | Rendering |
|---|---|---|---|
| Web | Remix.js + React | victory |
SVG/Canvas |
| Mobile | React Native | victory-native |
Native |
| Desktop | Electron + React | victory |
SVG/Canvas |
Code Reuse: ~90% of chart components shared across platforms
Comparison with Alternatives
| Feature | Victory | Chart.js | Recharts | Nivo |
|---|---|---|---|---|
| React Native Support | ✅ Native | ❌ None | ⚠️ Wrapper | ❌ None |
| Touch Gestures | ✅ Built-in | ⚠️ Limited | ⚠️ Limited | ✅ Good |
| Bundle Size | 50KB | 30KB | 45KB | 120KB |
| Mobile-First | ✅ Yes | ❌ No | ❌ No | ✅ Yes |
| Score | 9/10 | 5/10 | 6/10 | 6/10 |
Winner: Victory (only library with true cross-platform support)
2. Architecture Overview
Data Flow
Proxmark3 Hardware
↓ USB
Pi Zero 2 W Backend (Python)
↓ Execute PM3 command
PM3 Python Module (SWIG)
↓ Text output
Parser Layer (NEW)
↓ Structured JSON
FastAPI Endpoint (Enhanced)
↓ REST/SSE
Frontend (Remix/React)
↓ Victory Charts
User (Mobile/Desktop)
New Backend Components
Parser Module (app/backend/parsers/pm3_output.py)
"""
Convert PM3 text output into structured data for visualization.
"""
def parse_antenna_tuning(output: str) -> dict:
"""
Parse hw tune / hf tune / lf tune output.
Input: "# LF antenna: 50.00 V @ 125.00 kHz"
Output: {
"frequency": 125.0,
"voltage": 50.0,
"type": "lf",
"optimal": voltage > 45.0
}
"""
def parse_waveform_data(output: str) -> dict:
"""
Parse data samples command output.
Output: {
"samples": [1, 2, 3, ...],
"sample_rate": 48000,
"total_samples": 10000
}
"""
def parse_protocol_trace(output: str) -> dict:
"""
Parse hf list / lf list output into timeline.
Output: {
"frames": [
{
"timestamp": 1234,
"direction": "tag_to_reader",
"data": "0x01 0x02",
"crc": "ok"
},
...
]
}
"""
def parse_tag_detection(output: str) -> dict:
"""
Parse hf search / lf search results.
Output: {
"found": true,
"tag_type": "MIFARE Classic 1K",
"uid": "01234567",
"signal_strength": "good"
}
"""
Enhanced API Response
# app/backend/api/pm3.py
class CommandWithDataResponse(BaseModel):
"""Enhanced response with visualization data."""
success: bool
output: str # Original text (backwards compatible)
data: Optional[Dict] = None # Structured data for charts
visualization_type: Optional[str] = None # Chart type hint
# visualization_type values: "waveform", "tune", "trace", "summary"
@router.post("/command-with-data", response_model=CommandWithDataResponse)
async def execute_command_with_visualization(request: CommandRequest):
"""Execute PM3 command and return structured data."""
# Execute command
result = await pm3_worker.execute_command(request.command)
# Parse output based on command type
data = None
viz_type = None
if request.command.startswith(('hw tune', 'hf tune', 'lf tune')):
data = parse_antenna_tuning(result.output)
viz_type = "tune"
elif request.command.startswith('data'):
data = parse_waveform_data(result.output)
viz_type = "waveform"
elif request.command.startswith(('hf list', 'lf list')):
data = parse_protocol_trace(result.output)
viz_type = "trace"
elif request.command.startswith(('hf search', 'lf search')):
data = parse_tag_detection(result.output)
viz_type = "summary"
return CommandWithDataResponse(
success=result.success,
output=result.output,
data=data,
visualization_type=viz_type
)
Frontend Components
Shared Chart Library (app/shared/components/charts/)
Cross-platform components used by Web + React Native + Electron
// TuneChart.tsx
import { VictoryLine, VictoryChart, VictoryAxis, VictoryTheme } from 'victory'
interface TuneData {
frequency: number
voltage: number
optimal?: boolean
}
export function TuneChart({
data,
title,
thresholdVoltage = 40
}: {
data: TuneData[],
title: string,
thresholdVoltage?: number
}) {
return (
<VictoryChart
theme={VictoryTheme.material}
height={300}
padding={{ top: 40, bottom: 40, left: 60, right: 40 }}
>
<VictoryAxis
label="Frequency (kHz)"
style={{
axisLabel: { fontSize: 14, padding: 30, fill: '#9ca3af' }
}}
/>
<VictoryAxis
dependentAxis
label="Voltage (V)"
style={{
axisLabel: { fontSize: 14, padding: 40, fill: '#9ca3af' }
}}
/>
{/* Threshold line */}
<VictoryLine
data={[
{ x: Math.min(...data.map(d => d.frequency)), y: thresholdVoltage },
{ x: Math.max(...data.map(d => d.frequency)), y: thresholdVoltage }
]}
style={{
data: { stroke: "#ffaa00", strokeWidth: 1, strokeDasharray: "4,4" }
}}
/>
{/* Actual data */}
<VictoryLine
data={data}
x="frequency"
y="voltage"
style={{
data: { stroke: "#00ffff", strokeWidth: 3 }
}}
animate={{
duration: 500,
onLoad: { duration: 500 }
}}
/>
</VictoryChart>
)
}
// WaveformChart.tsx
import { VictoryLine, VictoryChart, VictoryZoomContainer } from 'victory'
export function WaveformChart({ samples }: { samples: number[] }) {
// Downsample if > 1000 points for performance
const displaySamples = samples.length > 1000
? downsample(samples, 1000)
: samples
const data = displaySamples.map((value, index) => ({ x: index, y: value }))
return (
<VictoryChart
height={400}
containerComponent={
<VictoryZoomContainer
zoomDimension="x"
allowPan={true}
allowZoom={true}
minimumZoom={{ x: 1 }}
/>
}
>
<VictoryLine
data={data}
style={{
data: { stroke: "#00ffff", strokeWidth: 2 }
}}
/>
</VictoryChart>
)
}
function downsample(data: number[], targetSize: number): number[] {
const step = Math.floor(data.length / targetSize)
return data.filter((_, i) => i % step === 0)
}
3. Guided Workflows
Framework Architecture
// app/frontend/app/components/GuidedWorkflow.tsx
interface WorkflowStep {
id: string
title: string
description: string
component: React.ComponentType<StepProps>
validation?: (data: any) => boolean
helpText?: string
}
interface StepProps {
data: Record<string, any>
onUpdate: (data: Record<string, any>) => void
onComplete: () => void
}
export function GuidedWorkflow({
steps,
onComplete
}: {
steps: WorkflowStep[],
onComplete: (data: any) => void
}) {
const [currentStep, setCurrentStep] = useState(0)
const [stepData, setStepData] = useState<Record<string, any>>({})
const canProceed = () => {
const step = steps[currentStep]
return !step.validation || step.validation(stepData)
}
const CurrentStepComponent = steps[currentStep].component
return (
<div className="workflow-container">
{/* Progress bar */}
<div className="progress-steps">
{steps.map((step, i) => (
<div
key={step.id}
className={`step ${i === currentStep ? 'active' : ''} ${i < currentStep ? 'completed' : ''}`}
>
{i < currentStep ? '✓' : i + 1} {step.title}
</div>
))}
</div>
{/* Current step */}
<div className="step-content">
<h2>{steps[currentStep].title}</h2>
<p>{steps[currentStep].description}</p>
<CurrentStepComponent
data={stepData}
onUpdate={(data) => setStepData({ ...stepData, ...data })}
onComplete={() => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1)
} else {
onComplete(stepData)
}
}}
/>
{steps[currentStep].helpText && (
<div className="help-text">{steps[currentStep].helpText}</div>
)}
</div>
{/* Navigation */}
<div className="step-navigation">
{currentStep > 0 && (
<button onClick={() => setCurrentStep(currentStep - 1)}>
← Back
</button>
)}
<button
onClick={() => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1)
} else {
onComplete(stepData)
}
}}
disabled={!canProceed()}
>
{currentStep < steps.length - 1 ? 'Next →' : 'Finish'}
</button>
</div>
</div>
)
}
Priority Workflows to Implement
1. HF/LF/HW Tune (Week 1-2)
Steps:
- Select frequency type (HF/LF/HW)
- Run tuning command
- Display real-time chart
- Show optimal/warning indicators
Files:
app/frontend/app/routes/workflows/tune.tsxapp/shared/components/charts/TuneChart.tsx
2. ID Transponder (Week 2-3)
Steps:
- Select frequency (HF/LF)
- Place tag on antenna
- Run detection command
- Display tag information card
Files:
app/frontend/app/routes/workflows/id-tag.tsx- Component: Tag info card with icon
3. Clone MIFARE Classic 1K (Week 3-4)
Steps:
- Tune HF antenna
- Read source card (64 blocks)
- Verify read success
- Write to target card
- Verify write success
Files:
app/frontend/app/routes/workflows/clone-mifare.tsx- Progress tracking component
4. Clone to T5577 (Week 4-5)
Steps:
- Tune LF antenna
- Read source tag
- Configure T5577 settings
- Write to T5577
- Verify clone
Files:
app/frontend/app/routes/workflows/clone-t5577.tsx
5. Sniffing Utility (Week 5-6)
Steps:
- Configure sniffing parameters
- Start capture
- Real-time frame display
- Stop capture
- Analyze captured data
Files:
app/frontend/app/routes/workflows/sniff.tsxapp/shared/components/charts/ProtocolTimeline.tsx
4. Implementation Roadmap
Phase 1: Foundation (Week 1)
Backend:
- Create
app/backend/parsers/pm3_output.py - Implement antenna tuning parser
- Add
/api/pm3/command-with-dataendpoint - Write parser unit tests
Frontend:
- Install Victory:
npm install victory - Create
app/shared/components/charts/directory - Implement
TuneChart.tsx - Test with mock data
Deliverable: Working HF/LF tune visualization
Phase 2: Guided Workflows (Week 2-3)
Frontend:
- Create
GuidedWorkflow.tsxframework - Implement HF/LF Tune workflow
- Implement ID Transponder workflow
- Add progress indicators
- Mobile touch optimization
Deliverable: Two working guided workflows
Phase 3: Advanced Workflows (Week 3-4)
Backend:
- Implement waveform parser
- Implement trace parser
- Add streaming support for sniffing
Frontend:
- Clone MIFARE Classic workflow
- Clone T5577 workflow
WaveformChart.tsxwith pan/zoom- Progress tracking components
Deliverable: Complete cloning workflows
Phase 4: Sniffing & Polish (Week 5-6)
Frontend:
- Sniffing utility implementation
ProtocolTimeline.tsxchart- Real-time data streaming
- Export capabilities (CSV/JSON)
Polish:
- Performance optimization
- Code splitting setup
- Mobile gesture improvements
- Accessibility improvements
Deliverable: Production-ready visualization system
5. Bundle Size Management
Code Splitting Strategy
// Lazy load charts only when needed
import { lazy, Suspense } from 'react'
const TuneChart = lazy(() => import('~/shared/components/charts/TuneChart'))
function TunePage() {
return (
<Suspense fallback={<div>Loading chart...</div>}>
<TuneChart data={data} />
</Suspense>
)
}
Bundle Impact Analysis
| Component | Size (gzipped) | When Loaded |
|---|---|---|
| Base App | ~120KB | Initial |
| Victory Core | +35KB | On-demand |
| TuneChart | +5KB | Workflow page |
| WaveformChart | +8KB | Workflow page |
| Total (worst case) | ~170KB | ✅ Acceptable |
Optimization: Most users won't load all charts in one session.
6. Cross-Platform Strategy
React Native App (Future)
// Same component, different import!
// Web version (Remix)
import { VictoryLine } from 'victory'
// React Native version
import { VictoryLine } from 'victory-native'
// Component code is IDENTICAL
export function TuneChart({ data }) {
return (
<VictoryChart>
<VictoryLine data={data} />
</VictoryChart>
)
}
BLE Integration for Mobile
Enhanced BLE Manager will support:
- Command execution via BLE (offline PM3 operations)
- Status queries via BLE
- Guided workflow triggers via BLE
- Real-time data streaming via BLE
Use Case: User operates PM3 with phone via BLE, no WiFi needed.
7. Testing Strategy
Backend Parser Tests
# test_pm3_parsers.py
def test_parse_antenna_tuning():
output = "# LF antenna: 50.00 V @ 125.00 kHz"
result = parse_antenna_tuning(output)
assert result["voltage"] == 50.0
assert result["frequency"] == 125.0
assert result["optimal"] == True # > 45V threshold
Frontend Chart Tests
// Mock data testing (no hardware needed)
const mockTuneData = [
{ frequency: 120, voltage: 45 },
{ frequency: 125, voltage: 50 },
{ frequency: 130, voltage: 48 }
]
test('TuneChart renders with mock data', () => {
render(<TuneChart data={mockTuneData} />)
expect(screen.getByText(/Voltage/)).toBeInTheDocument()
})
Mobile Touch Testing
- Test on actual smartphone (iPhone/Android)
- Verify pinch-zoom works smoothly
- Check 44px touch target compliance
- Test landscape orientation
8. Dependencies
NPM Packages
{
"dependencies": {
"victory": "^37.0.0", // ~35KB gzipped
"victory-native": "^37.0.0" // For React Native (later)
}
}
Python Packages
No new dependencies - use standard library for parsing.
9. Success Metrics
Technical
- Bundle size stays under 200KB (gzipped)
- Charts render in < 200ms on mobile
- Touch gestures work smoothly (60fps)
- Parser accuracy > 95% for all PM3 commands
UX
- Users complete workflows 80% faster than manual commands
- Mobile users can operate PM3 with one hand
- Reduce support requests by 50% (guided workflows)
10. Future Enhancements
After Initial Implementation
-
Waveform Annotations
- Mark interesting signal features
- Save/load annotated captures
-
Chart Exports
- Export as PNG/SVG
- Export data as CSV/JSON
- Share via mobile apps
-
Advanced Analytics
- Signal quality metrics
- Antenna tuning history tracking
- Success rate statistics
-
Offline Support
- Cache chart data locally
- Service worker for offline workflows
- Sync when reconnected
Summary
Victory Charts provides the perfect foundation for Dangerous Pi's visualization needs:
- ✅ True cross-platform support (Web, Mobile, Desktop)
- ✅ Mobile-first with touch gestures built-in
- ✅ Shared components = faster development
- ✅ Acceptable bundle size with code splitting
- ✅ Perfect for guided workflow UX
Next Step: Begin Phase 1 implementation (Backend parsers + Victory setup)
Questions? See UI_GUIDELINES.md for styling and claude.md for architecture details.