020b(gui): output-file Browse + scenario Save/Load buttons #33

Merged
ykp merged 1 commit from feat/020b-gui-file-buttons into main 2026-09-29 08:55:44 +02:00
Owner

Implements backlog step 020b (docs/backlog/020b-gui-file-buttons.md). Depends on 020a (merged in #32) — 020c (Generate-scenario) is unblocked by this.

What was missing

The GUI had no way to persist or restore a scenario: closing the window discarded everything, and sidecar *.scenario.json files written by the CLI could not be reopened from the GUI (reported and confirmed by inspection — the only buttons were Set/Remove offset and Generate).

Changes

src/gengnss/gui/app.py (Settings tab):

  • Browse… next to the Output-file entry — asksaveasfilename, default extension follows the fmt combobox (.i8/.i16); writes output_path through the same _on_setting path as the entry
  • Save scenario… — scenario_from_model(model).to_json() to a .scenario.json sidecar; refuses an invalid model (validate_settings, e.g. NaN spans) with a status-line message, no file written
  • Load scenario… — accepts both a bare to_json() dict and the full sidecar artifact written by generate_to_file ({"scenario": ...} wrapper) via the new scenario_from_sidecar helper; corrupt/wrong-shape JSON surfaces an error on the status line and leaves the model untouched
  • Load repopulates the UI: settings entries, fmt/placeholder combos, doppler/delay spans, and selections grouped by signal with specs rebuilt via sat_spec_for_system (checkbox checked first, then the spec var set, so the existing _on_sat_spec trace registers them)
  • resolved_seed is carried along for bookkeeping; output_path is deliberately not loaded (a sidecar travels with its IQ file) — the status line says output path unchanged
  • generation lock: all three buttons disable while a job runs

src/gengnss/gui/controllers.py: scenario_to_model(scenario, model) (inverse of scenario_from_model) and scenario_from_sidecar(data) (both JSON shapes).

Tests

  • tests/test_gui_load_save.py (headless, 5 tests): save→load round trip incl. GLONASS :chan + power offset; loading a generate_to_file sidecar in both shapes; corrupt-JSON rejection leaves the model unchanged; resolved_seed carried / output_path untouched; sat_spec_for_system specs re-parse to identical selections for all four systems
  • tests/test_gui_app.py: gui-marked smoke — buttons exist, lock behavior, Browse/Save/Load end-to-end with monkeypatched dialogs (checkbox + spec entry restored)
  • ruff clean; pytest: 197 passed, 1 skipped (gui-marked tests skip headless; run in CI with a display)
  • Headless shim smoke of the real handlers: 24/24 checks (020a + 020b) — Browse/Save/Load flows, save guard, corrupt-JSON guard

Note: CI legs 3.12/3.14 have been failing since run 119 on docs-only commits (known unreliable runner workspace per the CI header note); the 3.9 leg is green and local runs (Python 3.14.4) are green.

Implements backlog step **020b** (`docs/backlog/020b-gui-file-buttons.md`). Depends on 020a (merged in #32) — 020c (Generate-scenario) is unblocked by this. ## What was missing The GUI had no way to persist or restore a scenario: closing the window discarded everything, and sidecar `*.scenario.json` files written by the CLI could not be reopened from the GUI (reported and confirmed by inspection — the only buttons were Set/Remove offset and Generate). ## Changes `src/gengnss/gui/app.py` (Settings tab): - **Browse…** next to the Output-file entry — `asksaveasfilename`, default extension follows the fmt combobox (`.i8`/`.i16`); writes `output_path` through the same `_on_setting` path as the entry - **Save scenario…** — `scenario_from_model(model).to_json()` to a `.scenario.json` sidecar; refuses an invalid model (validate_settings, e.g. NaN spans) with a status-line message, no file written - **Load scenario…** — accepts both a bare `to_json()` dict and the full sidecar artifact written by `generate_to_file` (`{"scenario": ...}` wrapper) via the new `scenario_from_sidecar` helper; corrupt/wrong-shape JSON surfaces an error on the status line and leaves the model untouched - Load repopulates the UI: settings entries, fmt/placeholder combos, doppler/delay spans, and selections grouped by signal with specs rebuilt via `sat_spec_for_system` (checkbox checked first, then the spec var set, so the existing `_on_sat_spec` trace registers them) - `resolved_seed` is carried along for bookkeeping; `output_path` is deliberately **not** loaded (a sidecar travels with its IQ file) — the status line says `output path unchanged` - generation lock: all three buttons disable while a job runs `src/gengnss/gui/controllers.py`: `scenario_to_model(scenario, model)` (inverse of `scenario_from_model`) and `scenario_from_sidecar(data)` (both JSON shapes). ## Tests - `tests/test_gui_load_save.py` (headless, 5 tests): save→load round trip incl. GLONASS `:chan` + power offset; loading a `generate_to_file` sidecar in both shapes; corrupt-JSON rejection leaves the model unchanged; `resolved_seed` carried / `output_path` untouched; `sat_spec_for_system` specs re-parse to identical selections for all four systems - `tests/test_gui_app.py`: gui-marked smoke — buttons exist, lock behavior, Browse/Save/Load end-to-end with monkeypatched dialogs (checkbox + spec entry restored) - `ruff` clean; `pytest`: **197 passed, 1 skipped** (gui-marked tests skip headless; run in CI with a display) - Headless shim smoke of the real handlers: 24/24 checks (020a + 020b) — Browse/Save/Load flows, save guard, corrupt-JSON guard Note: CI legs 3.12/3.14 have been failing since run 119 on docs-only commits (known unreliable runner workspace per the CI header note); the 3.9 leg is green and local runs (Python 3.14.4) are green.
020b(gui): output-file Browse + scenario Save/Load buttons
Some checks failed
ci / test (3.12) (push) Failing after 1m15s
ci / test (3.14) (push) Failing after 1m20s
ci / test (3.12) (pull_request) Failing after 2m15s
ci / test (3.9) (push) Successful in 2m52s
ci / test (3.14) (pull_request) Failing after 2m13s
ci / test (3.9) (pull_request) Successful in 2m26s
461fbcdfa6
Implements docs/backlog/020b-gui-file-buttons.md (depends on 020a, merged
in #32):

- Browse… next to the Output-file entry (Settings tab): asksaveasfilename
  with the extension matching the fmt combobox (.i8/.i16); writes the path
  through the same _on_setting path as the entry
- Save scenario…: serializes scenario_from_model(model).to_json() to a
  sidecar JSON (defaultextension .scenario.json); refuses an invalid model
  (validate_settings) with a status-line message instead of writing NaN
- Load scenario…: accepts both a bare to_json() dict and the full sidecar
  artifact written by generate_to_file ({"scenario": ...} wrapper) via the
  new controllers.scenario_from_sidecar helper; corrupt/wrong-shape JSON
  surfaces an error on the status line and leaves the model untouched
- controllers.scenario_to_model: inverse of scenario_from_model (maps fs,
  center, duration, fmt, full scale, seed, resolved_seed, spans,
  placeholder bits, selections, power offsets); output_path deliberately
  not loaded (a sidecar travels with its IQ file) — status line says so
- selections -> UI: grouped by signal, sat-spec strings rebuilt with
  sat_spec_for_system; checkbox checked before the spec var is set so the
  existing _on_sat_spec trace registers them (idempotent)
- generation lock: all three buttons disabled while a job runs

Tests: tests/test_gui_load_save.py (headless: round trip incl. GLONASS
:chan + power offset, generate_to_file sidecar load in both shapes,
resolved_seed carried / output_path untouched, corrupt JSON rejection,
spec re-parse round trip); gui-marked smoke in tests/test_gui_app.py
(buttons exist, lock behavior, Browse/Save/Load with monkeypatched
dialogs). ruff clean; pytest 197 passed, 1 skipped (headless GUI).
ykp merged commit 623a114660 into main 2026-09-29 08:55:44 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ykp/gengnss!33
No description provided.