fix: hand-corrected CQ/ITU zones came back wrong at every restart
The profile's MY_* metadata is derived from the callsign through cty.dat, and the effect that derives it ran on every load — so opening the settings counted as "the source changed" and overwrote whatever the operator had typed. Reported by an operator in CQ 4 / ITU 4 handed 5 and 9: he corrected them, and each restart put 5 and 9 back. cty.dat gives the zones of the ENTITY, and a large country spans several, so the automatic value cannot be treated as authoritative — it is a starting point. A load now fills only fields that are EMPTY; a recompute that overwrites happens only when the callsign or grid itself changes, which is the case the derivation exists for. The load-vs-edit distinction is a ref holding the profile id the values were last derived from — first sight of a profile is a load.
This commit is contained in:
@@ -1,4 +1,14 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.21.9",
|
||||||
|
"date": "2026-07-28",
|
||||||
|
"en": [
|
||||||
|
"CQ and ITU zones you correct by hand in your profile stay corrected. They are derived from cty.dat, which gives the zones of the whole entity — in a country spanning several, the automatic value is wrong and it came back at every restart. They now only fill in when empty, and recompute when the callsign or grid changes."
|
||||||
|
],
|
||||||
|
"fr": [
|
||||||
|
"Les zones CQ et ITU corrigées à la main dans votre profil restent corrigées. Elles proviennent de cty.dat, qui donne les zones de l'entité entière — dans un pays qui en couvre plusieurs, la valeur automatique est fausse et revenait à chaque redémarrage. Elles ne se remplissent désormais que si le champ est vide, et se recalculent quand l'indicatif ou le locator change."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.21.8",
|
"version": "0.21.8",
|
||||||
"date": "2026-07-28",
|
"date": "2026-07-28",
|
||||||
|
|||||||
@@ -1483,27 +1483,49 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
return () => { off(); };
|
return () => { off(); };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Which (profile, callsign, grid) the auto-fill below last derived from.
|
||||||
|
// Without it, simply OPENING the settings counted as "the source changed" and
|
||||||
|
// overwrote the operator's own values.
|
||||||
|
const derivedFrom = useRef<string>('');
|
||||||
|
|
||||||
// Auto-fill the active profile's MY_* DXCC metadata from the station
|
// Auto-fill the active profile's MY_* DXCC metadata from the station
|
||||||
// callsign (country, DXCC#, CQ/ITU zones) and the grid (lat/lon). These
|
// callsign (country, DXCC#, CQ/ITU zones) and the grid (lat/lon).
|
||||||
// are derived values, so they always recompute when the callsign or grid
|
//
|
||||||
// changes — the user can still edit a field, it just re-populates when the
|
// These are derived values, so they recompute when the callsign or grid
|
||||||
// source changes. Debounced so we don't hammer cty.dat while typing.
|
// changes. What they must NOT do is recompute on a plain load: cty.dat gives
|
||||||
|
// the zones of the ENTITY, and a large country spans several — an operator in
|
||||||
|
// CQ 14 / ITU 27 was handed 15 and 28 and corrected them by hand, and every
|
||||||
|
// restart put the wrong pair back. cty.dat is a starting point, not an
|
||||||
|
// authority, so on a load we only fill fields that are EMPTY; a value the
|
||||||
|
// operator typed stays until the callsign or grid itself changes.
|
||||||
|
// Debounced so we don't hammer cty.dat while typing.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const call = (activeProfile?.callsign ?? '').trim();
|
const call = (activeProfile?.callsign ?? '').trim();
|
||||||
if (!call) return;
|
if (!call) return;
|
||||||
const grid = (activeProfile?.my_grid ?? '').trim();
|
const grid = (activeProfile?.my_grid ?? '').trim();
|
||||||
|
const source = `${activeProfile?.id ?? 0}|${call.toUpperCase()}|${grid.toUpperCase()}`;
|
||||||
|
// First sight of this profile — a load, not an edit.
|
||||||
|
const isLoad = derivedFrom.current === '' || derivedFrom.current.split('|')[0] !== String(activeProfile?.id ?? 0);
|
||||||
const t = window.setTimeout(async () => {
|
const t = window.setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const i: any = await ComputeStationInfo(call, grid);
|
const i: any = await ComputeStationInfo(call, grid);
|
||||||
|
derivedFrom.current = source;
|
||||||
setActiveProfile((p) => {
|
setActiveProfile((p) => {
|
||||||
if (!p) return p;
|
if (!p) return p;
|
||||||
const patch: any = {};
|
const patch: any = {};
|
||||||
if (i.country) patch.my_country = i.country;
|
// On a load, keep in patch only what the profile does not already have.
|
||||||
if (i.dxcc) patch.my_dxcc = i.dxcc;
|
const put = (k: string, v: any) => {
|
||||||
if (i.cqz) patch.my_cqz = i.cqz;
|
if (!v) return;
|
||||||
if (i.ituz) patch.my_ituz = i.ituz;
|
const cur = (p as any)[k];
|
||||||
if (i.lat) patch.my_lat = i.lat;
|
if (isLoad && cur !== undefined && cur !== null && cur !== '' && cur !== 0) return;
|
||||||
if (i.lon) patch.my_lon = i.lon;
|
patch[k] = v;
|
||||||
|
};
|
||||||
|
put('my_country', i.country);
|
||||||
|
put('my_dxcc', i.dxcc);
|
||||||
|
put('my_cqz', i.cqz);
|
||||||
|
put('my_ituz', i.ituz);
|
||||||
|
put('my_lat', i.lat);
|
||||||
|
put('my_lon', i.lon);
|
||||||
// Only re-render when a value actually changed (prevents loops).
|
// Only re-render when a value actually changed (prevents loops).
|
||||||
const changed = Object.keys(patch).some((k) => (p as any)[k] !== patch[k]);
|
const changed = Object.keys(patch).some((k) => (p as any)[k] !== patch[k]);
|
||||||
return changed ? { ...p, ...patch } : p;
|
return changed ? { ...p, ...patch } : p;
|
||||||
|
|||||||
Reference in New Issue
Block a user