diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 251496a..f1b42f2 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2879,12 +2879,12 @@ export default function App() {
);
const rstTxBlock = (
{t('field.rstTx')}
- { setRstSent(v); rstUserEditedRef.current = true; }} />
+ { setRstSent(v); rstUserEditedRef.current = true; }} />
);
const rstRxBlock = (
{t('field.rstRx')}
- { setRstRcvd(v); rstUserEditedRef.current = true; }} />
+ { setRstRcvd(v); rstUserEditedRef.current = true; }} />
);
// DX country flag, shown large next to RST (moved here from the Country field).
diff --git a/frontend/src/components/QSOEditModal.tsx b/frontend/src/components/QSOEditModal.tsx
index e0867c5..cfe6196 100644
--- a/frontend/src/components/QSOEditModal.tsx
+++ b/frontend/src/components/QSOEditModal.tsx
@@ -412,9 +412,9 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b
value={draft.callsign ?? ''} onChange={(e) => set('callsign', e.target.value)} />
S
- set('rst_sent', v)} />
+ set('rst_sent', v)} />
R
- set('rst_rcvd', v)} />
+ set('rst_rcvd', v)} />
{looking ? : } {t('qedit.fetch')}
diff --git a/frontend/src/components/ui/combobox.tsx b/frontend/src/components/ui/combobox.tsx
index 073cfbf..9549a76 100644
--- a/frontend/src/components/ui/combobox.tsx
+++ b/frontend/src/components/ui/combobox.tsx
@@ -64,7 +64,17 @@ export function Combobox({
// Focus selects the text so a keystroke replaces it — but does NOT
// open the list (so tabbing in doesn't pop the dropdown).
onFocus={(e) => { setQuery(value); e.currentTarget.select(); }}
- onChange={(e) => { setQuery(e.target.value); setOpen(true); if (commitOnType) onChange(e.target.value); }}
+ onChange={(e) => {
+ const v = e.target.value;
+ setQuery(v);
+ setOpen(true);
+ // Commit-on-type pushes the value live to the parent (so a CW macro sent
+ // without leaving the field uses what was just typed). With free text that's
+ // any input; a restricted field (allowFreeText=false) commits ONLY a value
+ // that's actually in the list, so a half-typed or invalid report never
+ // becomes the committed value — blur then reverts the leftover text.
+ if (commitOnType && (allowFreeText || options.some((o) => o.toLowerCase() === v.trim().toLowerCase()))) onChange(v);
+ }}
onBlur={onBlur}
onKeyDown={(e) => {
if ((e.key === 'ArrowDown' || e.key === 'Alt') && !open) { setOpen(true); }