Skip to content

feat: export profiles on codeplug project #122

Description

@pskillen

Problem

The internal Codeplug model is (and must remain) vendor-agnostic — one canonical representation of channels, zones, contacts, and talk groups regardless of which CPS formats we export to.

In practice, operators design a codeplug with specific radios in mind (Baofeng DM-1701, DM-32, UV-5R, …). Each radio/profile imposes different constraints: LCD name length, max channels, zone member slots, feature availability, and how logical channels denormalise into CPS rows (#46 multi-mode shipped; #36 multi-talkgroup imminent).

Today:

  • Profile limits are enforced (or silently violated) only at export time — see #106 (standardised export validation report).
  • CodeplugProject.targetRadios is a free-text operator note — explicitly not used for import, export, or validation (codeplug-project docs).
  • #86 sketches target-radio hint validation in CRUD but does not cover per-profile display names or the denormalised naming problem.

Operators discover length/capacity issues late, and cannot pre-configure shorter names for radios with tight LCD limits without polluting the canonical channel name.

Intended outcome

Introduce export profiles on the codeplug project wrapper (CodeplugProject) — a structured, machine-readable set of target radios the operator selects when setting up a project. Export profiles drive compatibility hints during editing and profile-aware behaviour at export — without adding vendor fields to Channel, Zone, or other Codeplug entities.

Vendor neutrality is non-negotiable. Export profiles and their overrides live on CodeplugProject (or project-scoped adjunct types). The Codeplug payload stays format-agnostic.

What an export profile enables

For each selected export profile (e.g. opengd77:baofeng-1701, dm32:dm-32uv, chirp:uv-5r):

Capability Example
Compatibility warnings Channel name exceeds 16-char LCD limit; project has >1023 channels; zone has >80 members
Per-profile display names Canonical MB7IDQ AlexandriaMB7IDQ Alex'ria on 1701 only
Export-time application Serialiser uses override names and profile caps; emits structured report (#106)
Pre-export validation validateExport(codeplug, { profileIds }) runs against project profiles before download

Hints are warnings by default (non-blocking save) unless a rule is explicitly elevated to error at export boundary — consistent with vendor boundaries.

Distinction from targetRadios

Field Type Purpose
targetRadios (existing) string[] free text Operator notes — "I use this on my 1701 and DM32"
exportProfiles (new) string[] registry ids Machine-readable profile selection driving hints + export

Do not overload targetRadios. Consider UI that links them (suggest profile when operator types a known radio) but keep data separate.

Data model — illustrative (CodeplugProject)

Final shape TBD during design; must stay on the project wrapper:

/** Registry slug — e.g. 'opengd77:baofeng-1701', 'chirp:uv-5r' */
type ExportProfileId = string;

interface CodeplugProject {
  // …existing metadata…
  targetRadios: string[];       // unchanged — operator notes
  exportProfiles: ExportProfileId[];  // new — structured selection
  /** Per-profile display names for entities / denormalised variants */
  exportNameOverrides: ExportNameOverride[];
}

interface ExportNameOverride {
  profileId: ExportProfileId;
  /** Internal entity id (channel, zone, …) */
  entityId: string;
  /**
   * Identifies a denormalised export row when one logical entity expands
   * to many CPS channels (multi-mode × multi-TG). Omitted for 1:1 entities.
   */
  variantKey?: string;
  displayName: string;
}
  • Default: exportProfiles: [], exportNameOverrides: [] — current behaviour.
  • Persisted in localStorage with schema migration.
  • Overrides are export labels, not internal FKs — canonical Channel.name remains the project source of truth.

Denormalised naming — the m×n problem

Logical channels can expand to multiple CPS rows at export:

Feature Status Expansion
Multi-mode (#46) Shipped 1 logical channel → m rows (one per mode profile)
Multi-talkgroup (#36) Imminent 1 logical channel → n rows (one per TG)
Combined Future edge case Up to m×n DMR rows; analogue/FM is always a single row → m×n + 1 at worst when both features enabled

Export profiles must support per-variant overrides keyed by variantKey (design during implementation — e.g. mode:fm, mode:dmr+tgid:235, or a stable denormalisation id from the export layer).

Compatibility warnings for name length must evaluate the effective export name (override if set, else generated denormalised name, else canonical name) per profile.

Export adapters own denormalisation; the profile layer consumes a stable variant enumeration API rather than re-implementing expansion logic in CRUD.

Profile registry

Machine-readable constraints mirror reference docs (docs/reference/opengd77/radios/, CHIRP radios, future DM32):

  • src/lib/exportProfiles/ (or extend src/lib/radioProfiles/ from feat: optional target-radio validation hints in CRUD UI #86) — registry.ts + per-profile modules
  • Each profile: { id, label, formatId, constraints, nameLimits, cardinalityCaps, featureFlags }
  • Rules emit shared code ids correlating with #106 export reports and CRUD hints

MVP profiles: Baofeng 1701 (OpenGD77), at least one CHIRP radio; DM32 when #67 ships.

Validation engine

Build on #86 sketch; extend for overrides and denormalised variants:

Function Purpose
validateChannelProfileHints(channel, profiles, overrides) Name length, unsupported features, effective export names
validateZoneProfileHints(zone, codeplug, profiles) Member count vs profile cap
validateCodeplugProfileHints(codeplug, project) Aggregate caps (channel count, etc.)
resolveExportDisplayName(channel, profileId, variantKey, overrides) Effective name for hint + serialiser
enumerateChannelVariants(channel, profileId) Delegates to export denormalisation planner

Reuse ValidationIssue (severity: 'warning') from src/lib/validation/channel.ts for CRUD hints.

UI surfaces

Surface Behaviour
Project setup / edit (#60) Multi-select export profiles from registry (not free text)
Channel edit / detail Warning when effective export name exceeds profile limit; inline override editor per selected profile (and per variant when expanded)
List / summary Compatibility badge per entity; project banner for aggregate caps (#61)
Export page Pre-select project's exportProfiles; run #106 validation per profile; allow session override

Style: amber compatibility warnings — distinct from save-blocking CRUD errors (#81).

Relationship to other tickets

Ticket Relationship
#86 Superseded in scope — fold hint validation into export profiles; close #86 when this ships or re-scope #86 as a slice
#106 Dependency — standardised ExportReportIssue / pre-export UI; profiles feed validateExport({ profileIds })
#72 Shipped — per-format profile picker at export; project profiles should default export UI selection
#36 Coordination — variant keys and generated names for multi-TG expansion
#46 Coordination — variant keys for multi-mode expansion
#67 Future DM32 profile in registry

Suggested slice order: profile registry → exportProfiles on project → hint engine (no overrides) → #106 integration → name overrides → multi-mode/TG variant UI.

Affected

  • src/models/codeplugProject.ts + storage migration
  • src/state/codeplugStore.tsxupdateProject, hint cache invalidation
  • src/lib/exportProfiles/ (new registry) — or rename/consolidate with feat: optional target-radio validation hints in CRUD UI #86's radioProfiles
  • src/lib/validation/profileHints.ts (new)
  • src/lib/export/ — denormalisation variant enumeration; apply overrides in serialisers
  • Project edit UI — replace/augment free-text TargetRadiosEditor with structured profile picker
  • Channel CRUD — per-profile override fields
  • docs/features/codeplug-project/, docs/features/import-export/, per-format radio reference docs
  • Tests: profile rules, override resolution, variant enumeration, hint UI smoke

Out of scope

  • Vendor-specific fields on Channel / Zone / Contact entities
  • Blocking save on compatibility warnings (unless explicitly designed per rule at export only)
  • Auto-inferring export profiles from import source (optional follow-up)
  • Guaranteed auto-generation of shortened names (operator configures overrides; suggest/copy helpers are follow-ups)

Manual verify

  1. Project with no export profiles → no hint UI; export unchanged.
  2. Select Baofeng 1701 profile → 20-char channel name shows warning on edit + list; save succeeds.
  3. Set per-profile override MB7IDQ Alex'ria → warning clears for 1701; canonical name unchanged.
  4. Multi-mode channel with 2 mode profiles → override UI shows 2 variant rows for profile with separate limits.
  5. Export page pre-selects project profiles; #106 report reflects profile caps.
  6. targetRadios free-text notes still work independently of exportProfiles.

Workflow note

Large multi-commit epic: branch from origin/main, atomic conventional commits per slice (registry → project fields → hints → export integration → overrides → variant UI → docs/tests). PR(s) linking Closes #. Consider progress log under docs/features/codeplug-project/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestimport-exportquality-of-lifefeature to improve the QOL of the person making a codeplug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions