Problem
Export adapters (OpenGD77 shipped; CHIRP imminent; DM32, qDMR, and others planned) must surface errors and warnings when the internal codeplug does not map cleanly to a vendor wire format — truncation, unsupported fields, cardinality caps, unresolvable references, and profile-specific limits.
Today there is no shared contract for those messages:
- OpenGD77 export serialises and downloads with no structured report (#95 — e.g. silent zone-member truncation beyond 80 slots).
ExportFromActivePanel is format-specific (hard-wired to opengd77ExportAdapter) and has no pre-export or post-export feedback UI.
- Import already has a message shape (
ImportResult.errors / skipped as { fileName, message } in src/lib/import/types.ts), but export has no equivalent.
- CRUD validation uses
ValidationIssue (field, message, severity) in src/lib/validation/channel.ts — vendor-neutral, entity-scoped — but export adapters do not participate.
As the exporter list grows, we risk one-off UI per format unless we standardise now.
Intended outcome
Introduce a vendor-neutral export validation/report format and a single UI surface that any export adapter can feed — including pre-export validation the operator can review before clicking download.
This ticket is a building block for #86 (optional target-radio validation hints in CRUD). #86 needs a stable way to surface compatibility warnings during editing; this ticket defines the shared report shape and export-page UX that #86 can reuse and extend.
Design principles
| Layer |
Role |
| Internal model + CRUD validation |
Vendor-neutral errors only (required fields, FK integrity) — unchanged |
| Export validation (new) |
Format/profile-specific errors (block export) and warnings (export proceeds with truncation/loss) |
| Export UI (new) |
One report component — grouped, filterable, navigable — not per-adapter bespoke panels |
Export validation lives at the import/export boundary (src/lib/export/), consistent with vendor boundaries.
Proposed report shape (illustrative — final API TBD)
Shared types e.g. src/lib/export/types.ts:
export type ExportReportSeverity = 'error' | 'warning';
export type ExportReportComponent =
| 'channels'
| 'zones'
| 'contacts'
| 'talkGroups'
| 'rxGroupLists'
| 'project';
export interface ExportReportIssue {
severity: ExportReportSeverity;
component: ExportReportComponent;
/** Internal entity id when the issue is entity-scoped; omit for project-level */
entityId?: string;
/** Internal field or wire column when helpful for UI anchoring */
field?: string;
/** CPS file affected when relevant (symmetry with import `ImportMessage.fileName`) */
fileName?: string;
message: string;
/** Machine-readable rule id for tests and future #86 hint correlation */
code?: string;
}
export interface ExportValidationResult {
issues: ExportReportIssue[];
/** false when any error-severity issue is present */
canExport: boolean;
}
Grouping: issues are tagged by component so the UI can show per-section summaries (e.g. "3 channel warnings, 1 zone error") and deep-link to CRUD detail routes where entityId is set.
Pre-export vs on-export: adapters expose validateExport(codeplug, options?) → ExportValidationResult for the export page preview; serialisation may append additional issues discovered during wire conversion (same shape). Options may later carry radio profile id (#72) without changing the report type.
Adapter contract
Extend export adapter registry (src/lib/export/registry.ts):
| Method |
Purpose |
validateExport(codeplug, options?) |
Pre-export scan — no file generation |
export(codeplug, options?) |
Returns { files, report: ExportValidationResult } (or equivalent) instead of silent download-only side effects |
Migrate OpenGD77 first; CHIRP export (imminent) should implement the contract from day one.
Known OpenGD77 issues to fold in (#95):
- Zone member truncation beyond 80 → warning with entity ids
- Channel count /
Channel Number over 1023 → warning
- RX group list truncation (32) → warning (test exists; wire into report)
Contributor checklist — adding-a-new-vendor.md
Required deliverable. Update docs/features/import-export/adding-a-new-vendor.md so every future format ships with standard export validation — not ad-hoc per-adapter UI or silent truncation.
Concrete updates:
§2 Code layout / adapter contract (export) — expand the one-line export contract to require:
§4 Tests — add row/scenario:
§5 UI — add checklist items:
§7 Feature docs — ensure adding-a-new-vendor.md itself is listed (self-referential maintenance).
Worked example (§8) — add OpenGD77 validateExport / report emission to the walk-through table once shipped.
This keeps CHIRP and subsequent formats on the standard path from day one.
UI surfaces
| Surface |
Behaviour |
Export page (/export) |
On format/profile selection, run validateExport and show report before download buttons; disable download when canExport === false |
| Report panel (shared component) |
Severity badges, component sections, expandable issue rows; link to entity detail when entityId present |
| Post-download |
Optional toast/summary if on-export issues differ from pre-export scan |
Decouple ExportFromActivePanel from OpenGD77-specific adapter wiring — drive from exportAdapters registry + selected vendorFormat.
Style: errors red, warnings amber — distinct from CRUD save-blocking errors and from #86 compatibility hints (which are advisory during edit).
Relationship to #86
| This ticket (#106) |
#86 |
| Export-time and pre-export report format + export-page UI |
CRUD-time warning hints driven by project target radios |
ExportReportIssue at format/profile boundary |
ValidationIssue with severity: 'warning' during edit |
Shared code ids where rules overlap (e.g. zone member cap) |
Optional: CRUD hints reference same rule codes for consistent copy |
| Ships first |
Consumes report types / UI patterns; may call validateExport with project target profiles |
#86 explicitly lists pre-export correlation ("12 hints would become export errors") as nice-to-have — this ticket makes that possible.
Affected
src/lib/export/types.ts (new shared types)
src/lib/export/registry.ts — adapter interface
src/lib/export/opengd77/ — validateExport, emit issues from serialise.ts
src/components/ExportFromActivePanel/ — registry-driven, report UI
- New shared component e.g.
src/components/ExportReportPanel/
docs/features/import-export/adding-a-new-vendor.md — export validation checklist (required; see above)
docs/features/import-export/README.md — implementation status row
- Tests: unit tests per adapter rule; UI smoke on export page with fixture codeplug triggering warnings/errors
Out of scope
Manual verify
- OpenGD77 export with a clean codeplug → empty report, downloads enabled.
- Zone with >80 members → pre-export warning listing zone + dropped members; download still allowed (warning) or blocked per rule severity.
- Switch export format in UI → same report panel layout, different adapter messages (no OpenGD77-only markup).
- Issue with
entityId → link navigates to correct channel/zone detail.
- Error-severity issue → download buttons disabled until resolved.
adding-a-new-vendor.md checklist includes export validation items; a new contributor can follow it without reading this issue.
Workflow note
Multi-commit feature: branch from origin/main, atomic conventional commits (shared types → OpenGD77 validate → report UI → registry decouple → adding-a-new-vendor checklist → docs/tests), PR linking Closes #106.
Problem
Export adapters (OpenGD77 shipped; CHIRP imminent; DM32, qDMR, and others planned) must surface errors and warnings when the internal codeplug does not map cleanly to a vendor wire format — truncation, unsupported fields, cardinality caps, unresolvable references, and profile-specific limits.
Today there is no shared contract for those messages:
ExportFromActivePanelis format-specific (hard-wired toopengd77ExportAdapter) and has no pre-export or post-export feedback UI.ImportResult.errors/skippedas{ fileName, message }insrc/lib/import/types.ts), but export has no equivalent.ValidationIssue(field,message,severity) insrc/lib/validation/channel.ts— vendor-neutral, entity-scoped — but export adapters do not participate.As the exporter list grows, we risk one-off UI per format unless we standardise now.
Intended outcome
Introduce a vendor-neutral export validation/report format and a single UI surface that any export adapter can feed — including pre-export validation the operator can review before clicking download.
This ticket is a building block for #86 (optional target-radio validation hints in CRUD). #86 needs a stable way to surface compatibility warnings during editing; this ticket defines the shared report shape and export-page UX that #86 can reuse and extend.
Design principles
Export validation lives at the import/export boundary (
src/lib/export/), consistent with vendor boundaries.Proposed report shape (illustrative — final API TBD)
Shared types e.g.
src/lib/export/types.ts:Grouping: issues are tagged by
componentso the UI can show per-section summaries (e.g. "3 channel warnings, 1 zone error") and deep-link to CRUD detail routes whereentityIdis set.Pre-export vs on-export: adapters expose
validateExport(codeplug, options?) → ExportValidationResultfor the export page preview; serialisation may append additional issues discovered during wire conversion (same shape). Options may later carry radio profile id (#72) without changing the report type.Adapter contract
Extend export adapter registry (
src/lib/export/registry.ts):validateExport(codeplug, options?)export(codeplug, options?){ files, report: ExportValidationResult }(or equivalent) instead of silent download-only side effectsMigrate OpenGD77 first; CHIRP export (imminent) should implement the contract from day one.
Known OpenGD77 issues to fold in (#95):
Channel Numberover 1023 → warningContributor checklist —
adding-a-new-vendor.mdRequired deliverable. Update
docs/features/import-export/adding-a-new-vendor.mdso every future format ships with standard export validation — not ad-hoc per-adapter UI or silent truncation.Concrete updates:
§2 Code layout / adapter contract (export) — expand the one-line export contract to require:
validateExport(codeplug, options?)returningExportValidationResultExportReportIssuefrom serialisation for truncation, lossy conversion, and profile cap violationscomponent,entityId, andfileNamewhere applicablecodeids for rules that may correlate with feat: optional target-radio validation hints in CRUD UI #86 hints laterExportReportPanel§4 Tests — add row/scenario:
code)§5 UI — add checklist items:
exportAdapters; no hard-wiring inExportFromActivePanelcanExport === falsedisables buttons§7 Feature docs — ensure
adding-a-new-vendor.mditself is listed (self-referential maintenance).Worked example (§8) — add OpenGD77
validateExport/ report emission to the walk-through table once shipped.This keeps CHIRP and subsequent formats on the standard path from day one.
UI surfaces
/export)validateExportand show report before download buttons; disable download whencanExport === falseentityIdpresentDecouple
ExportFromActivePanelfrom OpenGD77-specific adapter wiring — drive fromexportAdaptersregistry + selectedvendorFormat.Style: errors red, warnings amber — distinct from CRUD save-blocking errors and from #86 compatibility hints (which are advisory during edit).
Relationship to #86
ExportReportIssueat format/profile boundaryValidationIssuewithseverity: 'warning'during editcodeids where rules overlap (e.g. zone member cap)validateExportwith project target profiles#86 explicitly lists pre-export correlation ("12 hints would become export errors") as nice-to-have — this ticket makes that possible.
Affected
src/lib/export/types.ts(new shared types)src/lib/export/registry.ts— adapter interfacesrc/lib/export/opengd77/—validateExport, emit issues fromserialise.tssrc/components/ExportFromActivePanel/— registry-driven, report UIsrc/components/ExportReportPanel/docs/features/import-export/adding-a-new-vendor.md— export validation checklist (required; see above)docs/features/import-export/README.md— implementation status rowOut of scope
validateExportoptions should accept profile id when feat(export): OpenGD77 radio profile selection at export time #72 landsOPENGD77_MAX_ZONE_MEMBERSout of CRUD (#95 / epic: pristine vendor-neutral internal data model (MVP) #93) — related cleanup, not required to land the report formatManual verify
entityId→ link navigates to correct channel/zone detail.adding-a-new-vendor.mdchecklist includes export validation items; a new contributor can follow it without reading this issue.Workflow note
Multi-commit feature: branch from
origin/main, atomic conventional commits (shared types → OpenGD77 validate → report UI → registry decouple → adding-a-new-vendor checklist → docs/tests), PR linkingCloses #106.