Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions .claude/skills/new-radio/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,38 @@ not the opening move, and this has paid off on every radio where it was tried.
| GitHub RE repos for the model or its siblings | Config-file structure, command tables, enum tables |
| **The radio's own manual** | Menu order, option lists, defaults — and it is a *published source*, not a fallback |

### ★ First enumerate what the RADIO has, not what a source describes

Do this before reading any source in depth, and write it down. A published table
covers what its author needed; the gap between that and the radio is invisible
unless you have the radio's own list to hold it against.

1. **What is this radio FOR?** Read the model's feature list — the manual's
first pages, the manufacturer's product page. A built-in TNC, a GPS, D-STAR,
a second receiver, cross-band repeat: each is a whole family of settings.
Manufacturer naming often carries it (Kenwood's `D` in TM-**D**710 and
TH-**D**75 means the data/APRS half; the TM-V71 is the same radio without it).
2. **Enumerate the complete menu map** from the manual — every group, and how
many menus are in each. This is the denominator for everything after it.
3. **For each group, name the transport that reaches it.** A group with no
transport is a **finding**, not an omission, and it belongs in `PLAN.md`
before a line of code.

⚠ **One command's coverage is not the radio's settings.** The TM-D710 (#113)
shipped a 35-field settings schema built on its `MU` command, every field
measured on the radio and correct — and **no APRS at all**, on a radio whose
headline feature is APRS. `MU` carries menus 000-5xx; the APRS and TNC settings
are the 600-series and there is no `MU` parameter for one of them. The radio's
own `aprs_capable` flag was set to `true` in the same session. Nobody counted
the menus, so nobody noticed the settings stopped at 500.

The earlier note that "`MU` is not exhaustive — menus 504, 505 and 506 have no
parameter" was already in `FINDINGS.md`. It was read as a three-menu gap instead
of the question it actually was: *what else is missing, and how would we know?*

**Ask that question out loud in `PLAN.md`, with a number.** "The manual lists N
menus in G groups; this transport reaches M of them; the other N-M are <where>."

Then classify, because it decides how much of this process applies:

- **Clone of a family already supported** — AT-D868UV/D578 against the D890UV,
Expand All @@ -49,9 +81,14 @@ Then classify, because it decides how much of this process applies:
record-by-record programming (AnyTone).

**Gate:** a `PLAN.md` in `scratchpad/<driver_key>/` naming the sources found,
the programming medium, the family, and what the user owns. Template in
`templates/PLAN.md`. Nothing is written before this exists — it is also the
thing that makes a resumed session cheap.
the programming medium, the family, and what the user owns — **plus the menu
census above: how many menus the radio has, how many the chosen transport
reaches, and where the rest live.** Nothing is written before this exists; it is
also the thing that makes a resumed session cheap.

⚠ If the census cannot be completed because a group's transport is unknown, that
is the finding to report, not a detail to settle later. A radio shipped with a
whole feature's settings missing looks finished from the inside.

## 2. Anchor on a file the radio wrote

Expand Down Expand Up @@ -178,7 +215,19 @@ Then wire *both* ends, and check each off explicitly:
- [ ] **`apply_settings` called by the export path**
- [ ] the table↔schema agreement test

**Gate:** a test proving an export carries memories **and** settings together.
- [ ] **the coverage check against step 1's menu census** — the schema's field
count and groups reconciled against the menus the radio actually has, with
every absence named

**Gate:** a test proving an export carries memories **and** settings together,
and a **stated count**: N of the radio's M menus are exposed, and the M-N are
listed with a reason. "35 fields" is not a result; "35 of the 42 this transport
reaches, and the transport reaches 42 of the radio's ~90" is.

⚠ A cheap mechanical version of that reconciliation: the seed row already
asserts what the radio can do. A model with `aprs_capable: true` and no APRS
field in its settings schema is a contradiction the test suite can catch on its
own, and the TM-D710 shipped exactly that pairing for a whole session.

⚠ The fourth box is the one that nearly shipped broken. The read path worked and
the form filled correctly, so nothing looked wrong — the values simply never
Expand Down Expand Up @@ -235,6 +284,14 @@ if the folder is empty, the process above still stands on its own.

## Traps, each of which has already cost time

- ★ **A source's coverage is not the radio's.** Every field measured off one
command can be right and the set still be badly incomplete — the TM-D710
shipped a correct 35-field settings schema with no APRS on an APRS radio,
because `MU` stops at menu 500 and nobody counted the menus. Enumerate what
the radio HAS first, then hold every source against it.
- ★ **A noted gap is a question, not a footnote.** "`MU` is not exhaustive —
three menus have no parameter" sat in the findings for two sessions. It was
the same fact as "an entire feature is unreachable", written small.
- A working **read** path hides a dead **write** path. Verify the write.
- A printed option list is **display** order, not the stored index. One radio
prints High/Medium/Low and stores Low as 0.
Expand Down
27 changes: 27 additions & 0 deletions .claude/skills/new-radio/templates/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ re-measured.
**What the user has:** radio / cable / microSD card / programming software
(RT Systems, OEM CPS, none) / availability for hardware steps.

## ★ What this radio IS — the census

Fill this in **before** reading any source in depth. A published table covers
what its author needed; the gap is invisible without the radio's own list.

**Headline features** (from the manual's first pages, not from a driver): TNC /
APRS · GPS · D-STAR · DMR · second receiver · cross-band repeat · weather alert
· … Each one is a whole family of settings, and the model name often says so
(Kenwood's `D` in TM-**D**710 is the data/APRS half; the TM-V71 is the same
radio without it).

**Menu census** — the denominator for everything downstream:

| menu group | what it covers | how many | transport that reaches it |
|---|---|---|---|
| 0xx | | | |
| 1xx | | | |
| … | | | |
| **total** | | **N** | **M reached, N-M elsewhere** |

⚠ A group with **no** transport is a finding, not an omission — write it here
and say so out loud, with the number. "This command reaches M of N menus; the
other N-M are in \<where\>." The TM-D710 shipped a correct 35-field settings
schema with **no APRS at all** on an APRS radio, because `MU` stops at menu 500
and nobody ever counted. Every field in it was measured and right; the set was
the problem.

## Shape of the work

`driver_key = "<manufacturer>_<model>"`, `export_format = "<key>"`,
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ substantial work under `src-tauri/src/radios/`.
three OSes before it lands, which is the point: a branch that has never been
verified anywhere but the author's Mac should not reach `main`. This reverses
the old rule, which existed only because a PR cost metered minutes.
- **⚠ A radio model is finished work only when the whole model is done.** Keep
pushing the branch — that is what runs CI — but do not open a PR per phase or
per hardware step, and do not treat an open one as something to keep
merge-ready commit by commit. One PR, opened when the radio is essentially
complete: channels and settings both working, the hardware ladder climbed.
- **`main` is still verified on its own.** CI runs on push to `main` as well, so
a merge of two green branches gets checked as the combination — this project
has shipped bugs that existed nowhere else. Landing by local merge is still
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ card — or exported as CSV for tools that expect it.
| **Icom ID-52** | D-STAR + Analog | VHF / UHF TX, 108–174 / 225–479 MHz RX | microSD — patches the radio's own `.icf` file | 1000 memories in 100 groups; memories and menu settings restore in one operation |
| **Kenwood TH-D72** | APRS + Analog | 2 m / 70 cm TX, 118–174 / 320–524 MHz RX | Direct USB — read, write, settings | 1000 memories; 113 menu settings over the radio's own `MU` command |
| **Kenwood TH-D75** | D-STAR + APRS + Analog | VHF / 1.25 m / UHF TX, 0.1–524 MHz RX | microSD — patches the radio's own `.d75` file | 1000 memories in 30 groups; memories and menu settings, including the APRS setup |
| **Kenwood TM-D710** | APRS + Analog | 2 m / 70 cm TX, 118–524 MHz RX | Direct USB — read, write, settings | 1000 memories; 35 menu settings. Programmed live, one memory at a time over the operation panel's COM port — there is no image file, so a backup is a transcript of the radio's own lines |
| **Binteradio BT-9000** | Analog FM/NFM | 18–64 / 136–174 / 200–260 / 400–520 MHz TX, 18–520 MHz RX | Direct USB — read, write, settings | 960 channels in 15 fixed zones; 42 menu settings. Also sold as the Radtel RT-950 Pro, Bajeton BJ-9000 and Tenway TP-900 Pro — the radio reports itself as `RT-950` |

Direct USB programming reads the radio's current image, applies your changes, backs up the
Expand Down Expand Up @@ -59,7 +60,6 @@ settings together, then verify on the actual radio before shipping.
| **AnyTone AT-D578UV** | DMR + Analog mobile | [#47](https://github.com/ww8l/codeplug-magic/issues/47) |
| **AnyTone AT-D868UV** | DMR + Analog handheld | [#51](https://github.com/ww8l/codeplug-magic/issues/51) |
| **Icom ID-51** | D-STAR + Analog handheld | [#50](https://github.com/ww8l/codeplug-magic/issues/50) |
| **Kenwood TM-D710** | APRS + Analog mobile | [#113](https://github.com/ww8l/codeplug-magic/issues/113) |
| **Icom ID-5100** | D-STAR + Analog mobile | [#49](https://github.com/ww8l/codeplug-magic/issues/49) |
| **Icom IC-9100** | HF / VHF / UHF base | [#45](https://github.com/ww8l/codeplug-magic/issues/45) |
| **Icom IC-7610** | HF / 6 m SDR base | [#46](https://github.com/ww8l/codeplug-magic/issues/46) |
Expand Down
21 changes: 12 additions & 9 deletions src-tauri/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ mod tests {
// Models are reintroduced one at a time (migration 0005 trimmed the
// original set): currently the Baofeng UV-5R, TIDRADIO TD-H3, AnyTone
// AT-D890UV, Yaesu FT5D, Icom ID-52, Kenwood TH-D75, Kenwood TH-D72
// and the Binteradio BT-9000. (0015 removed
// the Vero VR-N76 placeholder.) None of the last three has a migration
// of its own — seeding INSERTs new (manufacturer, model) rows, so a new
// model reaches existing databases on the next startup without one.
// the Binteradio BT-9000 and Kenwood TM-D710. (0015 removed the Vero
// VR-N76 placeholder.) None of the last four has a migration of its
// own — seeding INSERTs new (manufacturer, model) rows, so a new model
// reaches existing databases on the next startup without one.
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM radio_models")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(
count.0, 8,
"expected the UV-5R, TD-H3, AT-D890UV, FT5D, ID-52, TH-D75, TH-D72 and BT-9000 \
seeded models"
count.0, 9,
"expected the UV-5R, TD-H3, AT-D890UV, FT5D, ID-52, TH-D75, TH-D72, BT-9000 \
and TM-D710 seeded models"
);

let models: Vec<(String,)> =
Expand All @@ -82,7 +82,10 @@ mod tests {
let names: Vec<&str> = models.iter().map(|m| m.0.as_str()).collect();
assert_eq!(
names,
vec!["AT-D890UV", "BT-9000", "FT5D", "ID-52", "TD-H3", "TH-D72", "TH-D75", "UV-5R"]
vec![
"AT-D890UV", "BT-9000", "FT5D", "ID-52", "TD-H3", "TH-D72", "TH-D75", "TM-D710",
"UV-5R"
]
);

// Seeding twice must remain idempotent.
Expand All @@ -91,7 +94,7 @@ mod tests {
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(count2.0, 8, "seeding should be idempotent");
assert_eq!(count2.0, 9, "seeding should be idempotent");

// A new database starts with NO talkgroups. The BrandMeister list used
// to be compiled in and seeded here; it is downloaded on request now,
Expand Down
Loading