This commit is contained in:
Jack Kingsman
2026-03-06 23:48:24 -08:00
parent 3edc7d9bd1
commit f302cc04ae
9 changed files with 259 additions and 21 deletions
@@ -43,6 +43,7 @@ export function SettingsRadioSection({
const [lat, setLat] = useState('');
const [lon, setLon] = useState('');
const [txPower, setTxPower] = useState('');
const [pathHashMode, setPathHashMode] = useState('0');
const [freq, setFreq] = useState('');
const [bw, setBw] = useState('');
const [sf, setSf] = useState('');
@@ -73,6 +74,7 @@ export function SettingsRadioSection({
setLat(String(config.lat));
setLon(String(config.lon));
setTxPower(String(config.tx_power));
setPathHashMode(String(config.path_hash_mode));
setFreq(String(config.radio.freq));
setBw(String(config.radio.bw));
setSf(String(config.radio.sf));
@@ -145,6 +147,7 @@ export function SettingsRadioSection({
const parsedLat = parseFloat(lat);
const parsedLon = parseFloat(lon);
const parsedTxPower = parseInt(txPower, 10);
const parsedPathHashMode = parseInt(pathHashMode, 10);
const parsedFreq = parseFloat(freq);
const parsedBw = parseFloat(bw);
const parsedSf = parseInt(sf, 10);
@@ -159,11 +162,20 @@ export function SettingsRadioSection({
return null;
}
if (
config.path_hash_mode_supported &&
(isNaN(parsedPathHashMode) || parsedPathHashMode < 0 || parsedPathHashMode > 2)
) {
setError('Path hash mode must be between 0 and 2');
return null;
}
return {
name,
lat: parsedLat,
lon: parsedLon,
tx_power: parsedTxPower,
...(config.path_hash_mode_supported && { path_hash_mode: parsedPathHashMode }),
radio: {
freq: parsedFreq,
bw: parsedBw,
@@ -384,6 +396,26 @@ export function SettingsRadioSection({
</div>
</div>
<div className="space-y-2">
<Label htmlFor="path-hash-mode">Path Hash Mode</Label>
<select
id="path-hash-mode"
value={pathHashMode}
onChange={(e) => setPathHashMode(e.target.value)}
disabled={!config.path_hash_mode_supported}
className="w-full h-10 px-3 rounded-md border border-input bg-background text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="0">1 byte per hop</option>
<option value="1">2 bytes per hop</option>
<option value="2">3 bytes per hop</option>
</select>
<p className="text-xs text-muted-foreground">
{config.path_hash_mode_supported
? 'Controls the default hop hash width your radio uses for outbound routed paths.'
: 'Connected radio or firmware does not expose this setting.'}
</p>
</div>
<Separator />
<div className="space-y-2">
+2
View File
@@ -173,6 +173,8 @@ const baseConfig = {
lon: 0,
tx_power: 17,
max_tx_power: 22,
path_hash_mode: 0,
path_hash_mode_supported: true,
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
};
+2
View File
@@ -201,6 +201,8 @@ describe('App search jump target handling', () => {
lon: 0,
tx_power: 17,
max_tx_power: 22,
path_hash_mode: 0,
path_hash_mode_supported: true,
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
});
mocks.api.getSettings.mockResolvedValue({
@@ -157,6 +157,8 @@ describe('App startup hash resolution', () => {
lon: 0,
tx_power: 17,
max_tx_power: 22,
path_hash_mode: 0,
path_hash_mode_supported: true,
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
});
mocks.api.getSettings.mockResolvedValue({
+2
View File
@@ -40,6 +40,8 @@ function createConfig(overrides: Partial<RadioConfig> = {}): RadioConfig {
lon: -74.006,
tx_power: 10,
max_tx_power: 20,
path_hash_mode: 0,
path_hash_mode_supported: true,
radio: { freq: 915, bw: 250, sf: 10, cr: 8 },
...overrides,
};
+34 -1
View File
@@ -24,6 +24,8 @@ const baseConfig: RadioConfig = {
lon: 2,
tx_power: 17,
max_tx_power: 22,
path_hash_mode: 1,
path_hash_mode_supported: true,
radio: {
freq: 910.525,
bw: 62.5,
@@ -57,6 +59,7 @@ const baseSettings: AppSettings = {
};
function renderModal(overrides?: {
config?: RadioConfig;
appSettings?: AppSettings;
health?: HealthStatus;
onSaveAppSettings?: (update: AppSettingsUpdate) => Promise<void>;
@@ -83,7 +86,7 @@ function renderModal(overrides?: {
const commonProps = {
open: overrides?.open ?? true,
pageMode: overrides?.pageMode,
config: baseConfig,
config: overrides?.config ?? baseConfig,
health: overrides?.health ?? baseHealth,
appSettings: overrides?.appSettings ?? baseSettings,
onClose,
@@ -218,6 +221,36 @@ describe('SettingsModal', () => {
});
});
it('saves radio path hash mode through onSave', async () => {
const { onSave } = renderModal();
openRadioSection();
fireEvent.change(screen.getByLabelText('Path Hash Mode'), {
target: { value: '2' },
});
fireEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
path_hash_mode: 2,
})
);
});
});
it('disables path hash mode when the connected radio does not expose it', () => {
renderModal({
config: { ...baseConfig, path_hash_mode_supported: false },
});
openRadioSection();
expect(screen.getByLabelText('Path Hash Mode')).toBeDisabled();
expect(
screen.getByText('Connected radio or firmware does not expose this setting.')
).toBeInTheDocument();
});
it('renders selected section from external sidebar nav on desktop mode', async () => {
renderModal({
externalSidebarNav: true,
+3
View File
@@ -12,6 +12,8 @@ export interface RadioConfig {
lon: number;
tx_power: number;
max_tx_power: number;
path_hash_mode: number;
path_hash_mode_supported: boolean;
radio: RadioSettings;
}
@@ -20,6 +22,7 @@ export interface RadioConfigUpdate {
lat?: number;
lon?: number;
tx_power?: number;
path_hash_mode?: number;
radio?: RadioSettings;
}