feat(ui): protected bands panel + modal edit dialog + JSON import/export (ticket 45) #44
No reviewers
Labels
No labels
accessibility
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
ykp/gensig!44
Loading…
Reference in a new issue
No description provided.
Delete branch "protected-bands-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Round 9 W3 — the operator surface for protected bands (spec §4.2.5 is the authority; #42 shipped the W1 data layer, #43 the W2 notch stage).
The panel (spec §4.2.5, as requested)
A "Protected bands" LabelFrame under the frequency-bands panel (grid row 2, col 1; the action buttons shift down one row) — compact, exactly as specified: four buttons — Import / Export / Clear / Edit — plus the count label "x protected bands" where x =
count_protected_in_span(records overlapping the signal's occupied span[fc−BW/2, fc+BW/2]). When out-of-span records exist the label stays honest with a(N defined)suffix. The label refreshes on everyrefresh()and after each dialog closes; Clear enables only when records exist.The modal edit dialog (operator decision #5)
protected_bands_dialog.py(new): the full 4-column table (Fmin (MHz)/Fmax (MHz)/Transition (MHz)/Rejection (dB)) with add/delete rows and inline editing (the frequency-bands double-click Entry pattern). Edits happen on a copy; L17/L18 feedback renders in a status label (red when invalid, with the exact finding messages); OK commits toproject.ProtectedBands(marks dirty), Cancel discards.JSON import/export (operator decision #4)
protected_bands_io.py(new,core/io): the protected-bands file is a UTF-8 JSON list of{Fmin, Fmax, Transition, Rejection}records in the project-JSON shape. The reader is strict — non-JSON, non-list, non-object entries and malformed records raiseProtectedBandsParseErrorcarrying the record index; import is atomic (the whole file validates before the model changes, so no partial imports). Import offers a replace/append choice when the current list is non-empty; Export writes via the save-as dialog.Tests —
tests/test_ui_protected_bands.py(18)Headless tier (10): JSON round-trip, project-JSON shape, empty list, all four rejection paths (with record index), missing file, atomic import, count-helper agreement. Widget tier (8, skips headless like the other UI tests): panel builds (4 buttons + label), count label update with the
(2 defined)suffix, clear enablement, edit-dialog wiring, export→import round-trip, malformed-import error + model preservation, replace/append both paths, clear confirmation (accept/decline/no-dialog-when-empty).Gate
pytest: 1278 passed / 64 skipped (headless tier), coverage 93.51 %ruff+mypy: cleanNext: ticket 46 (W4) — the integration close-out.
CI (Python 3.11, tkinter installed) caught two widget-tier failures the headless tier cannot run (tkinter absent locally): 1. test_clear_button_enabled_only_when_non_empty — a REAL production gap: the Clear button was never wired to the records' presence (the refresh loop's policy lookup defaults unknown keys to enabled). Now _update_protected_bands_label (which runs after the widget-enable loop in refresh()) enables Clear exactly when records exist. 2. test_panel_count_label_updates — a fixture gap: the default window project has NO frequency bands, so count_protected_in_span correctly returns 0. The test now sets the bands fixture first. Also fixes the expected text to the module's own singular/plural logic (count == 1 → "1 protected band (2 defined)") and adds a plural case ("2 protected bands", no suffix). Headless simulation of both label paths verified; full gate green (1278 passed / 64 skipped, coverage 93.51 %; ruff + mypy clean).