Problem
The internal Channel model was bootstrapped from OpenGD77 CPS columns and most optional fields are untyped strings in the UI and model. The channel edit form exposes free-text inputs for values that are really numbers or enums, often carrying OpenGD77-specific wire values that are opaque in a vendor-neutral tool.
Examples today:
| Field |
Stored / UI today |
Desired internal semantics (illustrative) |
power |
P2, Master (OpenGD77) |
50% / 100% or low / high / full |
bandwidthKHz |
"12.5" string |
12.5 number or enum narrow / wide |
colourCode |
string |
1–15 integer |
timeslot |
string |
1 | 2 enum |
squelch |
75%, Disabled |
typed enum or numeric level |
rxOnly |
Yes / No string |
boolean |
transmitTimeout |
string seconds |
number |
rxTone / txTone |
free text |
structured CTCSS/DCS/none |
This makes validation weak, UI inconsistent, and export mappers pass vendor strings through unchanged rather than translating at the boundary.
Intended outcome
Manually rationalise each channel field — decide the vendor-neutral internal representation, document it, then update code and ETL.
Per-field deliverables
For each Channel field (and related entities as discovered):
- Decision — internal type, allowed values, default, whether vendor-specific values live only in import/export mappers.
- Documentation — update
docs/features/data-model/README.md with typed definitions and semantics.
- Reference doc — where values are domain knowledge (power levels, tones, bandwidths), add or extend
docs/reference/ entries.
- Model — update
src/models/codeplug.ts types and channelFieldDefaults().
- Migration — schema version bump; migrate existing localStorage projects (map old string values → new types).
- Import — OpenGD77 (
src/lib/import/opengd77/) maps vendor wire → internal model.
- Export — OpenGD77 (
src/lib/export/opengd77/) maps internal model → vendor wire.
- UI — channel edit/detail/list use appropriate controls (
Select, NumberInput, Checkbox, not TextInput) per field.
- Import/export docs — update
docs/features/import/opengd77.md and export docs with mapping tables.
Suggested field groups (non-exhaustive)
- RF:
bandwidthKHz, power, rxTone, txTone, squelch, rxOnly
- DMR:
colourCode, timeslot, dmrId
- Behaviour:
transmitTimeout, voxEnabled, scanSkip (some already boolean)
- Frequencies: consider numeric MHz internally vs string (separate decision)
Power is the canonical motivating example: internal powerLevel: 'low' | 'high' | 'full' (or %) with OpenGD77 export mapping low → P2, full → Master, etc.
Affected
src/models/codeplug.ts + migration
src/lib/import/opengd77/, src/lib/export/opengd77/
src/routes/channels/ (edit, detail, list)
src/lib/validation/ (if present) — typed validation rules
docs/features/data-model/, docs/features/import/opengd77.md, docs/features/export/
docs/reference/ — new field-value references as needed
Notes / dependencies
Out of scope
- Automatic inference of field types from CSV headers without explicit design decisions.
- Changing entity relationships (contacts, TG lists) — channel scalar fields only unless unavoidable.
Workflow note
Branch from origin/main, atomic conventional commits per field/group, PR linking Closes #. Use progress-tracking / feature-docs skills for a rationalisation checklist.
Problem
The internal
Channelmodel was bootstrapped from OpenGD77 CPS columns and most optional fields are untyped strings in the UI and model. The channel edit form exposes free-text inputs for values that are really numbers or enums, often carrying OpenGD77-specific wire values that are opaque in a vendor-neutral tool.Examples today:
powerP2,Master(OpenGD77)50%/100%orlow/high/fullbandwidthKHz"12.5"string12.5number or enumnarrow/widecolourCode1–15integertimeslot1|2enumsquelch75%,DisabledrxOnlyYes/NostringtransmitTimeoutrxTone/txToneThis makes validation weak, UI inconsistent, and export mappers pass vendor strings through unchanged rather than translating at the boundary.
Intended outcome
Manually rationalise each channel field — decide the vendor-neutral internal representation, document it, then update code and ETL.
Per-field deliverables
For each
Channelfield (and related entities as discovered):docs/features/data-model/README.mdwith typed definitions and semantics.docs/reference/entries.src/models/codeplug.tstypes andchannelFieldDefaults().src/lib/import/opengd77/) maps vendor wire → internal model.src/lib/export/opengd77/) maps internal model → vendor wire.Select,NumberInput,Checkbox, notTextInput) per field.docs/features/import/opengd77.mdand export docs with mapping tables.Suggested field groups (non-exhaustive)
bandwidthKHz,power,rxTone,txTone,squelch,rxOnlycolourCode,timeslot,dmrIdtransmitTimeout,voxEnabled,scanSkip(some already boolean)Power is the canonical motivating example: internal
powerLevel: 'low' | 'high' | 'full'(or%) with OpenGD77 export mappinglow → P2,full → Master, etc.Affected
src/models/codeplug.ts+ migrationsrc/lib/import/opengd77/,src/lib/export/opengd77/src/routes/channels/(edit, detail, list)src/lib/validation/(if present) — typed validation rulesdocs/features/data-model/,docs/features/import/opengd77.md,docs/features/export/docs/reference/— new field-value references as neededNotes / dependencies
vendorExtrasuntil a vendor adapter needs them.Out of scope
Workflow note
Branch from
origin/main, atomic conventional commits per field/group, PR linkingCloses #. Use progress-tracking / feature-docs skills for a rationalisation checklist.