Skip to content

Commit 269119b

Browse files
Implement VFO store with hardware bandwidth constraint validation (#292)
* Initial plan * Implement VFO store with type definitions, validation, and comprehensive tests Co-authored-by: alexthemitchell <5687158+alexthemitchell@users.noreply.github.com> * Fix VFO overlap detection logic to treat edge-adjacent VFOs as non-overlapping Co-authored-by: alexthemitchell <5687158+alexthemitchell@users.noreply.github.com> * Fix formatting in VFO implementation memory file Co-authored-by: alexthemitchell <5687158+alexthemitchell@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexthemitchell <5687158+alexthemitchell@users.noreply.github.com> Co-authored-by: Alex Mitchell <alex+github@alexmitchelltech.com>
1 parent eb063ad commit 269119b

File tree

5 files changed

+1550
-1
lines changed

5 files changed

+1550
-1
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# VFO Store Implementation - Phase 2 Complete
2+
3+
## Overview
4+
5+
Implemented Phase 2 of Multi-VFO architecture: Core data structures and Zustand store with constraint enforcement.
6+
7+
## File Structure
8+
9+
### Type Definitions
10+
11+
- **Location**: `src/types/vfo.ts`
12+
- **Exports**: VfoConfig, VfoState, VfoStatus enum, VfoMetrics, MIN_VFO_SPACING_HZ
13+
14+
### Store Slice
15+
16+
- **Location**: `src/store/slices/vfoSlice.ts`
17+
- **Pattern**: Follows existing Zustand slice pattern (frequencySlice, markerSlice, etc.)
18+
- **Exports**: VfoSlice interface, vfoSlice StateCreator, VfoValidationError, validateVfoConfig, detectVfoOverlap
19+
20+
### Integration
21+
22+
- **Location**: `src/store/index.ts`
23+
- **Additions**: VfoSlice added to RootState, useVfo convenience hook created
24+
25+
### Tests
26+
27+
- **Location**: `src/store/slices/__tests__/vfoSlice.test.ts`
28+
- **Coverage**: 36 tests covering all operations and constraints
29+
30+
## Key Implementation Details
31+
32+
### Validation Pattern
33+
34+
All VFO mutations (add, update) require a VfoValidationContext:
35+
36+
```typescript
37+
{
38+
hardwareCenterHz: number,
39+
sampleRateHz: number,
40+
}
41+
```
42+
43+
Store automatically adds existingVfos and maxVfos to build full context.
44+
45+
### Constraint Enforcement
46+
47+
1. **Max VFO count**: Throws VfoValidationError when maxVfos exceeded
48+
2. **Hardware bandwidth**: Validates center frequency and bandwidth edges are within capture range
49+
3. **Spacing warnings**: Console.warn for VFOs closer than MIN_VFO_SPACING_HZ (per spec: "allow overlap for now but warn")
50+
51+
### Resource Cleanup Pattern
52+
53+
removeVfo logs cleanup intentions but doesn't call dispose() - actual cleanup will be handled by MultiVfoProcessor (Phase 3).
54+
55+
### State Management
56+
57+
- VFO state is ephemeral (runtime-only, not persisted)
58+
- Default values: audioGain=1.0, priority=5, status=IDLE
59+
- Metrics initialized with rssi=-100, samplesProcessed=0
60+
61+
## Testing Approach
62+
63+
Test categories:
64+
65+
1. Initialization and basic CRUD
66+
2. Constraint validation (hardware bandwidth, max count, negative frequencies)
67+
3. Spacing warnings (console.warn spy)
68+
4. Edge cases (non-existent VFOs, boundary conditions)
69+
5. Helper functions (getAllVfos, getActiveVfos, detectVfoOverlap)
70+
71+
## Quality Gates Passed
72+
73+
- ✅ All 3112 unit tests pass (including 36 new VFO tests)
74+
- ✅ ESLint passes (import ordering, prettier formatting)
75+
- ✅ TypeScript type checking passes
76+
- ✅ Webpack build succeeds
77+
78+
## Next Phases
79+
80+
Phase 3: DSP Pipeline (MultiVfoProcessor, channelization, audio mixing)
81+
Phase 4: UI Components (useMultiVfo hook, VfoManager, VfoCard)
82+
Phase 5: Integration with useReception hook

src/store/index.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
signalLevelSlice,
5050
type SignalLevelSlice,
5151
} from "./slices/signalLevelSlice";
52+
import { vfoSlice, type VfoSlice } from "./slices/vfoSlice";
5253
import type { SignalLevel } from "../lib/measurement/types";
5354
import type { ISDRDevice } from "../models/SDRDevice";
5455

@@ -61,7 +62,8 @@ export type RootState = SettingsSlice &
6162
DeviceSlice &
6263
DiagnosticsSlice &
6364
MarkerSlice &
64-
SignalLevelSlice;
65+
SignalLevelSlice &
66+
VfoSlice;
6567

6668
/**
6769
* Create the root store with all slices
@@ -79,6 +81,7 @@ export const useStore = create<RootState>()(
7981
...diagnosticsSlice(...args),
8082
...markerSlice(...args),
8183
...signalLevelSlice(...args),
84+
...vfoSlice(...args),
8285
}),
8386
{ name: "rad.io-store" },
8487
),
@@ -317,6 +320,40 @@ export const useSignalLevel = (): {
317320
return { signalLevel, setSignalLevel, clearSignalLevel };
318321
};
319322

323+
// VFO slice selectors
324+
export const useVfo = (): Pick<
325+
RootState,
326+
| "vfos"
327+
| "maxVfos"
328+
| "addVfo"
329+
| "removeVfo"
330+
| "updateVfo"
331+
| "updateVfoState"
332+
| "setVfoAudio"
333+
| "clearVfos"
334+
| "getVfo"
335+
| "getAllVfos"
336+
| "getActiveVfos"
337+
| "setMaxVfos"
338+
> => {
339+
return useStore(
340+
useShallow((state: RootState) => ({
341+
vfos: state.vfos,
342+
maxVfos: state.maxVfos,
343+
addVfo: state.addVfo,
344+
removeVfo: state.removeVfo,
345+
updateVfo: state.updateVfo,
346+
updateVfoState: state.updateVfoState,
347+
setVfoAudio: state.setVfoAudio,
348+
clearVfos: state.clearVfos,
349+
getVfo: state.getVfo,
350+
getAllVfos: state.getAllVfos,
351+
getActiveVfos: state.getActiveVfos,
352+
setMaxVfos: state.setMaxVfos,
353+
})),
354+
);
355+
};
356+
320357
// Export types
321358
export type { SettingsState, VizMode } from "./slices/settingsSlice";
322359
export type { Marker } from "./slices/markerSlice";
@@ -332,3 +369,6 @@ export type {
332369
DecoderMetrics,
333370
SignalQualityMetrics,
334371
} from "./slices/diagnosticsSlice";
372+
export type { VfoValidationContext } from "./slices/vfoSlice";
373+
export type { VfoConfig, VfoState, VfoMetrics } from "../types/vfo";
374+
export { VfoStatus, MIN_VFO_SPACING_HZ } from "../types/vfo";

0 commit comments

Comments
 (0)