Summary
The decode list is append-only: every decode of a station adds a new row, so a station heard across several cycles shows up as many duplicate lines. Instead, keep one row per station and update it in place as new decodes arrive (SNR, frequency, last-heard time, message, etc.).
Add sorting so the operator can order that de-duplicated list by:
- Last heard (most recent decode first) — default
- Callsign (alphabetical)
- SNR (strongest first)
Current behavior
DecodeScreen renders mainViewModel.mutableFt8MessageList directly, one DecodeRow per Ft8Message, and auto-scrolls to the newest entry. The list is the raw decode stream — messages are appended in decode order and never coalesced by callsign — so a busy band fills the screen with repeats of the same stations.
Relevant code:
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/decode/DecodeScreen.kt — builds filteredMessages, time-group dividers, and the LazyColumn.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/decode/DecodeRow.kt — the row UI.
com.k1af.ft8af.MainViewModel#mutableFt8MessageList — the append-only decode stream.
Proposed behavior
- Collapse by callsign. Group the message stream by
callsignFrom and keep the latest decode per station (retaining fields the row shows: SNR, freq_hz, utcTime, message text, DXCC/continent flags). One row per station.
- Update in place. When a station is decoded again, refresh its existing row rather than adding a new one. Consider a subtle highlight/animation so the operator can see which rows just updated.
- Sorting control. Add a sort selector (last-heard / callsign / SNR) in the top bar or filter row. Persist the choice via
GeneralVariables + databaseOpr.writeConfig(...) like the existing msgMode / clearDecodesEveryCycle toggles.
Notes / open questions
- Do the coalescing in an extracted
internal pure function (e.g. collapseByStation(messages, sortMode)) so it's unit-testable per CLAUDE.md — keep the Composable a thin wrapper. Needs tests covering: duplicate collapse, each sort mode, and stable ordering on ties.
- Interaction with the existing time-group dividers (
computeTimeGroupDividers): once rows are one-per-station and sorted by something other than time, the per-cycle dividers no longer make sense. Decide whether to drop them in the collapsed view or only show them in "last heard" ordering.
- Interaction with "clear every cycle" mode and the auto-scroll to newest logic — both assume an append-only, time-ordered list and will need revisiting.
- Keep the collapsed view compatible with the existing filters (CQ, POTA, New DXCC, Needed, For Me) — collapse after filtering, or filter after collapse, whichever keeps counts correct.
Current behavior: the same station appears as multiple duplicate rows as it is decoded across cycles.
Summary
The decode list is append-only: every decode of a station adds a new row, so a station heard across several cycles shows up as many duplicate lines. Instead, keep one row per station and update it in place as new decodes arrive (SNR, frequency, last-heard time, message, etc.).
Add sorting so the operator can order that de-duplicated list by:
Current behavior
DecodeScreenrendersmainViewModel.mutableFt8MessageListdirectly, oneDecodeRowperFt8Message, and auto-scrolls to the newest entry. The list is the raw decode stream — messages are appended in decode order and never coalesced by callsign — so a busy band fills the screen with repeats of the same stations.Relevant code:
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/decode/DecodeScreen.kt— buildsfilteredMessages, time-group dividers, and theLazyColumn.ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/decode/DecodeRow.kt— the row UI.com.k1af.ft8af.MainViewModel#mutableFt8MessageList— the append-only decode stream.Proposed behavior
callsignFromand keep the latest decode per station (retaining fields the row shows: SNR,freq_hz,utcTime, message text, DXCC/continent flags). One row per station.GeneralVariables+databaseOpr.writeConfig(...)like the existingmsgMode/clearDecodesEveryCycletoggles.Notes / open questions
internalpure function (e.g.collapseByStation(messages, sortMode)) so it's unit-testable per CLAUDE.md — keep the Composable a thin wrapper. Needs tests covering: duplicate collapse, each sort mode, and stable ordering on ties.computeTimeGroupDividers): once rows are one-per-station and sorted by something other than time, the per-cycle dividers no longer make sense. Decide whether to drop them in the collapsed view or only show them in "last heard" ordering.Current behavior: the same station appears as multiple duplicate rows as it is decoded across cycles.