chore: release v0.23.2

This commit is contained in:
2026-08-02 23:51:46 +02:00
parent 3da1d71323
commit 9846354bf9
18 changed files with 514 additions and 24 deletions
+50 -2
View File
@@ -9121,6 +9121,49 @@ func (a *App) GetLogFilePath() string {
return applog.Path()
}
// TailLogFile returns the END of the diagnostic log — the last maxBytes, cut
// back to a line boundary so the first line is never a fragment.
//
// The tail, not the file: it rotates at 10 MB, and the live viewer polls this
// every second. Shipping megabytes across the Wails bridge on a timer would cost
// more than the whole rest of the UI.
func (a *App) TailLogFile(maxBytes int) (string, error) {
p := applog.Path()
if p == "" {
return "", fmt.Errorf("log file not initialised")
}
if maxBytes <= 0 || maxBytes > 4*1024*1024 {
maxBytes = 256 * 1024
}
f, err := os.Open(p)
if err != nil {
return "", err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return "", err
}
start := int64(0)
if fi.Size() > int64(maxBytes) {
start = fi.Size() - int64(maxBytes)
}
if _, err := f.Seek(start, io.SeekStart); err != nil {
return "", err
}
buf, err := io.ReadAll(f)
if err != nil {
return "", err
}
s := string(buf)
if start > 0 {
if i := strings.IndexByte(s, '\n'); i >= 0 {
s = s[i+1:]
}
}
return s, nil
}
// UILog lets the frontend write to the same diagnostic log as the backend.
//
// Frontend-only logic (entry-strip auto-fill, debounce ordering, which field the
@@ -11377,13 +11420,18 @@ func (a *App) consumeUDPEvents() {
// after the configured duration. De-duped per call in the Flex backend.
if a.catFlexDecodeSpots && a.cat != nil {
now := time.Now()
if t, seen := lastDecodeSpot[ev.DecodeCall]; seen && now.Sub(t) < decodeSpotWindow {
// Key by call AND 100 kHz slot, not by call alone: with two
// receivers running (50.313 and 50.400, say) a station heard on
// both is two legitimate spots on two panadapters, and a
// call-only key threw the second away.
key := fmt.Sprintf("%s@%d", ev.DecodeCall, ev.DecodeFreqHz/100_000)
if t, seen := lastDecodeSpot[key]; seen && now.Sub(t) < decodeSpotWindow {
continue // spotted this call very recently — don't re-hammer the radio
}
if len(lastDecodeSpot) > 4000 {
lastDecodeSpot = map[string]time.Time{} // bound memory on long sessions
}
lastDecodeSpot[ev.DecodeCall] = now
lastDecodeSpot[key] = now
secs := a.catFlexDecodeSecs
if secs <= 0 {
secs = 120