Summary
The aetherd RFC 2.3 SliceModel conversion (#4068) moved the slice-status seam payload from a stringly-keyed QVariantMap to a compiler-checked SliceDelta struct, so a field-name typo between the backend decode and the model apply is now a compile error rather than a silently-dropped field. The earlier touchpoints (#4065 pan, #4066 meter) still use QVariantMap for their extension/definition payloads — this issue tracks bringing them to parity so the whole seam is uniformly compiler-checked, plus de-duplicating the now-copied decode helpers.
Raised in review by @ten9876 (#4068, #4066) and @rfoust (#4068): "the untyped N-field QVariantMap is the fragility at scale — a typo compiles, passes the emit-only decode test, and silently drops that field forever." Fixed for slice; the same (smaller) risk remains for pan/meter.
Scope
1. Typed payloads for the remaining touchpoints
- Meter —
IRadioBackend::meterDefined(int, const QVariantMap&) → a typed MeterDelta/MeterDef-shaped struct (7 fields: source, sourceIndex, name, unit, low, high, description). RadioModel reconstructs MeterDef from it today via hand-matched string keys.
- Pan
panState extension — the extensionStatus("flex","panState", QVariantMap) bundle (8 fields incl. wide, loop A/B, fps, preamp, DAX-IQ, MultiFlex client_handle, waterfall stream-id) → a typed PanStateDelta. (The panWnb extension is a small 3-field group — fold in or leave, reviewer's call.)
- Model:
SliceDelta lives in src/core/backends/ so the interface stays free of model deps — follow the same placement for the new structs.
2. Shared decode helpers (#4068 finding #3)
The present-only carry helpers (oStr/oInt/oReal/oBool in decodeSliceStatus; str/integer/boolean carry pattern in decodePanState/decodePanExtensions/decodeMeterStatus) and splitList (a verbatim copy of the deleted SliceModel::splitAntennaList) are duplicated across the four decode methods. Centralize into one shared, ok-guarded carry-helper set so the present-only contract and the fail-closed numeric guard live in one place.
Explicitly out of scope (handled separately)
- Meter
num/low/hi fail-closed parse-guards — a live gap on main, being fixed directly (not deferred to this issue).
- TransmitModel (PR D, the final mixed model) — will use a typed
TransmitDelta from the outset, so it does not need retrofitting.
Why non-blocking / priority
Pan panState (8 fields) and meter (7 fields) are far smaller than slice's ~70, so the drift risk is real but low, and a programmatic key-symmetry check currently mitigates it. This is a robustness + consistency improvement, best done before the pattern is copied further — good candidate to land after the 2.3 mixed-model splits finish and before step 2.4.
Refs #3849 (aetherd RFC tracking). Related: #4065, #4066, #4068.
Summary
The aetherd RFC 2.3 SliceModel conversion (#4068) moved the slice-status seam payload from a stringly-keyed
QVariantMapto a compiler-checkedSliceDeltastruct, so a field-name typo between the backend decode and the model apply is now a compile error rather than a silently-dropped field. The earlier touchpoints (#4065 pan, #4066 meter) still useQVariantMapfor their extension/definition payloads — this issue tracks bringing them to parity so the whole seam is uniformly compiler-checked, plus de-duplicating the now-copied decode helpers.Raised in review by @ten9876 (#4068, #4066) and @rfoust (#4068): "the untyped N-field
QVariantMapis the fragility at scale — a typo compiles, passes the emit-only decode test, and silently drops that field forever." Fixed for slice; the same (smaller) risk remains for pan/meter.Scope
1. Typed payloads for the remaining touchpoints
IRadioBackend::meterDefined(int, const QVariantMap&)→ a typedMeterDelta/MeterDef-shaped struct (7 fields: source, sourceIndex, name, unit, low, high, description).RadioModelreconstructsMeterDeffrom it today via hand-matched string keys.panStateextension — theextensionStatus("flex","panState", QVariantMap)bundle (8 fields incl.wide, loop A/B,fps, preamp, DAX-IQ, MultiFlexclient_handle, waterfall stream-id) → a typedPanStateDelta. (ThepanWnbextension is a small 3-field group — fold in or leave, reviewer's call.)SliceDeltalives insrc/core/backends/so the interface stays free of model deps — follow the same placement for the new structs.2. Shared decode helpers (#4068 finding #3)
The present-only carry helpers (
oStr/oInt/oReal/oBoolindecodeSliceStatus;str/integer/booleancarry pattern indecodePanState/decodePanExtensions/decodeMeterStatus) andsplitList(a verbatim copy of the deletedSliceModel::splitAntennaList) are duplicated across the four decode methods. Centralize into one shared, ok-guarded carry-helper set so the present-only contract and the fail-closed numeric guard live in one place.Explicitly out of scope (handled separately)
num/low/hifail-closed parse-guards — a live gap onmain, being fixed directly (not deferred to this issue).TransmitDeltafrom the outset, so it does not need retrofitting.Why non-blocking / priority
Pan
panState(8 fields) and meter (7 fields) are far smaller than slice's ~70, so the drift risk is real but low, and a programmatic key-symmetry check currently mitigates it. This is a robustness + consistency improvement, best done before the pattern is copied further — good candidate to land after the 2.3 mixed-model splits finish and before step 2.4.Refs #3849 (aetherd RFC tracking). Related: #4065, #4066, #4068.