Background
PR #2644 landed the `PersistentDialog` base class and migrated `ProfileManagerDialog` as the proof-of-concept (-99 LOC on the dialog file). Six more dialogs in `MainWindow.cpp` use the same hand-rolled lazy-construct + non-modal + geometry-persist + frameless-chrome pattern and are ripe for the same subtractive migration.
The constitution Principle III note in PR #2605: these per-dialog reimplementations were a contributor papercut source (PR #2591 required 9 fixes during review for boilerplate alone). With the base class on main, future dialog conversions and new dialogs only need to wire the content layout via `bodyWidget()`.
Dialogs to migrate
Each follows the same pattern — change the base class, drop the hand-rolled members + lifecycle methods, port content layout into `bodyWidget()`, switch the MainWindow construct site to `showOrRaisePersistent()`.
| Dialog |
File |
Has geometry persist? |
Notes |
| `NetworkDiagnosticsDialog` |
`src/gui/NetworkDiagnosticsDialog.{cpp,h}` |
✓ |
Reference dialog (#2580 set the original pattern) |
| `MemoryDialog` |
`src/gui/MemoryDialog.{cpp,h}` |
✓ |
|
| `AetherDspDialog` |
`src/gui/AetherDspDialog.{cpp,h}` |
partial |
Geometry was added later; complete during migration |
| `DxClusterDialog` (SpotHub) |
`src/gui/DxClusterDialog.{cpp,h}` |
✓ |
Largest dialog (tabs, tables) |
| `MultiFlexDialog` |
`src/gui/MultiFlexDialog.{cpp,h}` |
— |
Add persist as part of migration |
| `MidiMappingDialog` |
`src/gui/MidiMappingDialog.{cpp,h}` |
— |
Add persist as part of migration |
| `PanLayoutDialog` |
`src/gui/PanLayoutDialog.{cpp,h}` |
— |
Add persist as part of migration |
(7 total counting `ProfileManagerDialog` which already landed.)
Per-dialog migration recipe
For each dialog `FooDialog`:
- Change `class FooDialog : public QDialog` → `class FooDialog : public PersistentDialog`.
- Replace the ctor signature `(.., QWidget* parent = nullptr) : QDialog(parent), ...` with `: PersistentDialog("Foo", "FooDialogGeometry", parent), ...`.
- Delete `setFramelessMode()`, `closeEvent()`, `moveEvent()`, `resizeEvent()`, `saveGeometryToSettings()`, `restoreGeometryFromSettings()`.
- Delete `m_titleBar`, `m_bodyLayout`, `m_restoringGeometry` members.
- Move content layout from a local `auto* bodyWidget = new QWidget(this); auto* root = new QVBoxLayout(bodyWidget);` to `auto* root = new QVBoxLayout(bodyWidget());`.
- Delete `FramelessResizer::install(this);` and the manual `setFramelessMode(AppSettings::instance().value("FramelessWindow", "True"))` call from the ctor — base class owns both.
- In MainWindow's menu callback, replace the lazy-construct boilerplate with `showOrRaisePersistent(m_fooDialog, ctorArgs...)`.
- Drop the dialog's explicit `setFramelessMode(on)` call in `MainWindow::setFramelessWindow` — auto-registered via `m_persistentDialogs`.
- Verify geometry key reuse — keep the existing AppSettings key (e.g. `MemoryDialogGeometry`) so users don't lose saved positions on upgrade.
Scope and risk
- Each migration is strictly subtractive for the dialog file (~50–100 LOC removed).
- Geometry-persist key reuse means zero user impact on upgrade.
- The three dialogs that don't currently persist geometry will gain it — a UX improvement, not a regression.
Pickup
- All six are AetherClaude-eligible — mechanical, well-scoped, single-file changes guided by the recipe above.
- Recommend one PR per dialog to keep review surface small.
- `NetworkDiagnosticsDialog` is the natural first one (it was the original frameless-chrome reference).
73, Jeremy KK7GWY & Claude (AI dev partner)
Background
PR #2644 landed the `PersistentDialog` base class and migrated `ProfileManagerDialog` as the proof-of-concept (-99 LOC on the dialog file). Six more dialogs in `MainWindow.cpp` use the same hand-rolled lazy-construct + non-modal + geometry-persist + frameless-chrome pattern and are ripe for the same subtractive migration.
The constitution Principle III note in PR #2605: these per-dialog reimplementations were a contributor papercut source (PR #2591 required 9 fixes during review for boilerplate alone). With the base class on main, future dialog conversions and new dialogs only need to wire the content layout via `bodyWidget()`.
Dialogs to migrate
Each follows the same pattern — change the base class, drop the hand-rolled members + lifecycle methods, port content layout into `bodyWidget()`, switch the MainWindow construct site to `showOrRaisePersistent()`.
(7 total counting `ProfileManagerDialog` which already landed.)
Per-dialog migration recipe
For each dialog `FooDialog`:
Scope and risk
Pickup
73, Jeremy KK7GWY & Claude (AI dev partner)