Problem / outcome
Operators often want a near-copy of an existing entity — e.g. a second channel with one field changed, a zone template with the same member order, or a talk group with the same DMR ID on a different slot. Today every entity must be recreated manually in CRUD.
Add a Duplicate action on each base codeplug entity detail page (and optionally list row actions) that creates a new entity from the source record.
In scope: entities on Codeplug — channel, zone, talk group, RX group list, contact.
Out of scope: duplicating a whole codeplug project (#31).
Behaviour
Shallow copy (default)
- Assign a new UUID
id.
- Copy all scalar / nested fields from the source.
- Disambiguate
name so validation passes (shared TG/contact namespace, zone/channel name uniqueness). Suggested default: append (copy) or (2), (3), … until unique.
- Preserve FK refs to other entities unchanged:
- Channel →
contactRef, rxGroupListId
- Zone → same
memberChannelIds / member entries (still pointing at the original channels)
- RX group list → same
memberRefs (talk groups / contacts)
- Clear or strip import/provenance fields that identify a specific remote listing (
meta.repeaterDirectory, wire-only provenance) so the copy is clearly operator-authored; keep benign operator metadata if any.
- After duplicate: navigate to the new entity’s edit or detail page.
UX
- Detail page header: Duplicate beside Edit / Delete (match existing action placement).
- Optional follow-up: list-row duplicate — only if it does not clutter tables; detail action is the MVP.
- Confirm when duplicating would create a name collision that cannot be auto-resolved (should be rare if suffix logic is solid).
Implementation notes
| Area |
Location |
| Mutations |
src/lib/codeplugMutations.ts — duplicateChannel, duplicateZone, … (thin wrappers over existing add* + field copy) |
| Name uniquification |
Reuse validation helpers / shared uniqueName utility; respect TG+contact shared namespace |
| Store |
codeplugStore expose duplicate* actions |
| UI |
src/routes/channels/detail.tsx, zones/detail.tsx, talk-groups/detail.tsx, rx-group-lists/detail.tsx, contacts/detail.tsx |
| Tests |
Mutation unit tests per entity; one smoke test that zone duplicate keeps member channel ids |
Vendor boundaries
Internal model only — no radio caps, no export wire names beyond what the entity already carries. Duplication does not belong in import/export adapters.
Acceptance criteria
Relates to
- #11 / #12 / #13 — CRUD foundation
- #31 — duplicate project (separate concern)
Workflow note
Branch from origin/main, atomic conventional commits per entity or per layer (mutations → store → UI), PR linking Closes #N.
Problem / outcome
Operators often want a near-copy of an existing entity — e.g. a second channel with one field changed, a zone template with the same member order, or a talk group with the same DMR ID on a different slot. Today every entity must be recreated manually in CRUD.
Add a Duplicate action on each base codeplug entity detail page (and optionally list row actions) that creates a new entity from the source record.
In scope: entities on
Codeplug— channel, zone, talk group, RX group list, contact.Out of scope: duplicating a whole codeplug project (#31).
Behaviour
Shallow copy (default)
id.nameso validation passes (shared TG/contact namespace, zone/channel name uniqueness). Suggested default: append(copy)or(2),(3), … until unique.contactRef,rxGroupListIdmemberChannelIds/ member entries (still pointing at the original channels)memberRefs(talk groups / contacts)meta.repeaterDirectory, wire-only provenance) so the copy is clearly operator-authored; keep benign operator metadata if any.UX
Implementation notes
src/lib/codeplugMutations.ts—duplicateChannel,duplicateZone, … (thin wrappers over existingadd*+ field copy)uniqueNameutility; respect TG+contact shared namespacecodeplugStoreexposeduplicate*actionssrc/routes/channels/detail.tsx,zones/detail.tsx,talk-groups/detail.tsx,rx-group-lists/detail.tsx,contacts/detail.tsxVendor boundaries
Internal model only — no radio caps, no export wire names beyond what the entity already carries. Duplication does not belong in import/export adapters.
Acceptance criteria
idand a uniquename.npm run testcovers mutation behaviour.Relates to
Workflow note
Branch from
origin/main, atomic conventional commits per entity or per layer (mutations → store → UI), PR linkingCloses #N.