A radio profile that has never been read off a radio is seeded with a schema
default for every field, saved with all of them, and then written to the radio
in full. The operator sees a form they never filled in, and the radio gets ~60
settings nobody chose.
The path
-
src/lib/profiles.ts — seedValues(fields, saved, defaults = true) fills any
field not present in the saved profile with fieldDefault(f):
| type |
seeded value |
boolean |
false |
integer |
field.min ?? 0 |
select |
options[0] — the first entry in the list |
text |
"" |
-
src/components/profiles/ProfileEditor.tsx — for a cable radio the third
argument is !isCardRadio = true, so defaults are seeded. It then saves the
whole map: non_channel_settings: JSON.stringify(values).
-
src-tauri/src/commands/program.rs — write_radio_settings reads
non_channel_settings back out of the DB and hands the entire object to
the driver. The driver's patch skips only keys that are absent or null; a
seeded default is neither.
So "create a profile, hit Write" pushes a value for every field in the schema.
Why it matters
Card radios are already exempt — isCardRadio turns defaults off, and the
comment there cites #90, where keying that decision off the wrong flag "seeded
~300 schema defaults into a file the radio itself wrote". The same reasoning
applies to a cable radio; it just was not extended, because a cable radio's
settings looked like they came from nowhere rather than from a file.
The blast radius grows with schema size. The TM-D710 now ships 95 controls, and
its first-option-in-the-list defaults include things like APRS beacon method,
data band, and TX delay.
⚠ This is not the empty-string case. That half was fixed for the TM-D710 in
ae7e718: image_settings::patch treats "" as "not set" and leaves the radio's
bytes alone, because otherwise a fresh profile would have blanked the operator's
call sign, all five status texts and all five position records. Selects and
booleans have no equivalent "unset" value, so they still go out.
Where to fix it
Probably not in each driver — this is the form layer, and every driver would need
the same guard. The shape that fits the existing design is to send only what the
operator or a radio read actually supplied:
- don't seed defaults for a cable radio either (
seedValues(..., false)), so an
untouched field is simply absent from the saved JSON and every driver's
existing "skip absent keys" logic already does the right thing; or
- keep the form's defaults for display but write only keys that differ from the
seeded baseline — ProfileEditor already tracks baseline for a related
reason.
The first is closer to how card radios already behave and to
no-default-settings-for-card-radios.
What a fix has to be checked against
Read a profile off a real radio, change one setting, write it, and confirm the
other 94 come back byte-identical — the TM-D710 is a good test bed because its
settings read/write round trip is hardware-proven and its backup captures both
transports.
Not urgent
Raised while shipping v26.9.6 (#113). Nothing is known to have been damaged by
it; it needs an operator to create a fresh profile and write it without reading
from the radio first.
A radio profile that has never been read off a radio is seeded with a schema
default for every field, saved with all of them, and then written to the radio
in full. The operator sees a form they never filled in, and the radio gets ~60
settings nobody chose.
The path
src/lib/profiles.ts—seedValues(fields, saved, defaults = true)fills anyfield not present in the saved profile with
fieldDefault(f):booleanfalseintegerfield.min ?? 0selectoptions[0]— the first entry in the listtext""src/components/profiles/ProfileEditor.tsx— for a cable radio the thirdargument is
!isCardRadio=true, so defaults are seeded. It then saves thewhole map:
non_channel_settings: JSON.stringify(values).src-tauri/src/commands/program.rs—write_radio_settingsreadsnon_channel_settingsback out of the DB and hands the entire object tothe driver. The driver's
patchskips only keys that are absent or null; aseeded default is neither.
So "create a profile, hit Write" pushes a value for every field in the schema.
Why it matters
Card radios are already exempt —
isCardRadioturns defaults off, and thecomment there cites #90, where keying that decision off the wrong flag "seeded
~300 schema defaults into a file the radio itself wrote". The same reasoning
applies to a cable radio; it just was not extended, because a cable radio's
settings looked like they came from nowhere rather than from a file.
The blast radius grows with schema size. The TM-D710 now ships 95 controls, and
its first-option-in-the-list defaults include things like APRS beacon method,
data band, and TX delay.
⚠ This is not the empty-string case. That half was fixed for the TM-D710 in
ae7e718:image_settings::patchtreats""as "not set" and leaves the radio'sbytes alone, because otherwise a fresh profile would have blanked the operator's
call sign, all five status texts and all five position records. Selects and
booleans have no equivalent "unset" value, so they still go out.
Where to fix it
Probably not in each driver — this is the form layer, and every driver would need
the same guard. The shape that fits the existing design is to send only what the
operator or a radio read actually supplied:
seedValues(..., false)), so anuntouched field is simply absent from the saved JSON and every driver's
existing "skip absent keys" logic already does the right thing; or
seeded baseline —
ProfileEditoralready tracksbaselinefor a relatedreason.
The first is closer to how card radios already behave and to
no-default-settings-for-card-radios.What a fix has to be checked against
Read a profile off a real radio, change one setting, write it, and confirm the
other 94 come back byte-identical — the TM-D710 is a good test bed because its
settings read/write round trip is hardware-proven and its backup captures both
transports.
Not urgent
Raised while shipping v26.9.6 (#113). Nothing is known to have been damaged by
it; it needs an operator to create a fresh profile and write it without reading
from the radio first.