diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cf3521c..afd2472 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2147,39 +2147,45 @@ export default function App() { // Split the macro on so the log happens AT the token's position, not // only at the very end: "TU QRZ" sends "TU", logs, then sends "QRZ". // Each split boundary (every part except the last) is one . - const parts = rawText.split(//i); + // RESOLVE every segment up front — while the form still holds the callsign — so a + // segment AFTER the (which logs and clears the form) still expands its + // variables correctly. + const parts = rawText.split(//i).map((pt) => resolveCW(pt)); const isRig = cwSourceRef.current === 'icom' || cwSourceRef.current === 'flex'; for (let p = 0; p < parts.length; p++) { if (aborted()) return; // ESC / Stop before this segment → stop sending, don't log - const resolved = resolveCW(parts[p]); + const resolved = parts[p]; + const isLast = p === parts.length - 1; + const logAfter = !isLast; // a follows this segment if (resolved) { // Trailing word space so back-to-back sends don't run together in the keyer // buffer ("CQ"+"TEST" → "CQTEST"); the keyer keys it as a word gap at the // current WPM, so it scales automatically. const keyed = resolved + ' '; setWkSent(resolved); - if (isRig) { - // The rig keyer (Icom 0x17 / Flex CWX) gives no busy echo we track, so - // wait the ESTIMATED send duration before moving on / logging. - const sendFn = cwSourceRef.current === 'flex' ? FlexSendCW : IcomSendCW; - await sendFn(keyed).catch((e) => setError(String(e?.message ?? e))); - await waitMs(Math.round(estimateCwMs(resolved, wkWpm)) + (p < parts.length - 1 ? 200 : 600)); - } else { - await WinkeyerSend(keyed).catch((e) => setError(String(e?.message ?? e))); - // Wait for the keyer to actually finish this segment before we log / - // continue. We'd like to watch busy rise then fall, but over a - // remote/serial-over-IP link that echo can lag tens of seconds, so cap - // the wait at the ESTIMATED send duration + slack: proceed when busy - // clears OR the estimate elapses, whichever comes first. - const capMs = Math.round(estimateCwMs(resolved, wkWpm) * 1.4) + 2500; - for (let i = 0; i < 20 && !wkBusyRef.current && !aborted(); i++) await sleep(50); // ≤1s to start - const deadline = Date.now() + capMs; - while (wkBusyRef.current && !aborted() && Date.now() < deadline) await sleep(50); + const sendFn = cwSourceRef.current === 'flex' ? FlexSendCW : cwSourceRef.current === 'icom' ? IcomSendCW : null; + if (sendFn) await sendFn(keyed).catch((e) => setError(String(e?.message ?? e))); + else await WinkeyerSend(keyed).catch((e) => setError(String(e?.message ?? e))); + // Only WAIT for the CW to finish on the FINAL segment — auto-call needs the + // send done before its gap+resend. A segment a follows is NOT waited + // on: the keyer has already buffered the CW, so logging happens AT ONCE and + // never interrupts what's being sent. (Gating the log on a CW-length ESTIMATE + // made log up to ~10s late when the estimate ran long.) + if (isLast) { + if (isRig) { + await waitMs(Math.round(estimateCwMs(resolved, wkWpm)) + 600); + } else { + // WinKeyer: watch the busy echo, capped by the estimate (+slack) since + // over a remote/serial-over-IP link that echo can lag tens of seconds. + const capMs = Math.round(estimateCwMs(resolved, wkWpm) * 1.4) + 2500; + for (let i = 0; i < 20 && !wkBusyRef.current && !aborted(); i++) await sleep(50); // ≤1s to start + const deadline = Date.now() + capMs; + while (wkBusyRef.current && !aborted() && Date.now() < deadline) await sleep(50); + } } } if (aborted()) return; // aborted while this segment was sending → don't log - // A token follows every part except the last → log the contact here. - if (p < parts.length - 1) void save(); + if (logAfter) void save(); // log NOW — the buffered CW keeps sending } } // stopAutoCall cancels any running auto-call loop.