#### Brief overview of PR changes/additions
- 43 of 45 functional tests now run with `detect_leaks=1` and fail CI if
they leak (the suite leaked 11.83MB before this PR). The LSan
suppression hooks move into an OBJECT library so they actually bind into
the test binaries.
- Fixes the three production leaks behind nearly all of it: the
unparented `QSettings` (now application-parented, since the Updater
holds the pointer past window close), edbee re-initialisation orphaning
its managers on every repeat `mudlet::init()`, and the preferences
dialog's menu `QAction`s (`QMenu::addAction()` does not take ownership).
- New `EdbeeReinitTest` and a preferences-reopen test lock the fixes in.
Still excluded: `dlgTriggerEditorUndoRedoTest` (fixed in #9700) and
`UpdaterTeardownTest` (Qt's one-time CA-store load).
#### Motivation for adding to Mudlet
Leaks in code covered by the functional suite now fail the build instead
of accumulating silently, and two of the three fixes stop real leaks in
production.
#### Other info (issues closed, discussion etc)
Test case: full serial suite passes twice, 45/45; settings persistence
verified end-to-end under Xvfb. Startup unchanged (median 213ms vs
214ms); the suite's +62s is LSan's at-exit scan, no individual test
regressed.
Assisted-by: Claude:claude-fable-5
#### Brief overview of PR changes/additions
- On macOS the `Updater` only creates its Sparkle wrapper in
`checkUpdatesOnStart()`, but the preferences dialog asks it about
automatic updates in its constructor - null dereference. All three
Sparkle-backed accessors are now guarded by a new `Updater::ready()`.
- The preferences hide the updates group box while the platform updater
isn't set up, rather than showing a checkbox that stores nothing.
- `DialogTeardownTest` gains a case that opens the preferences with
`DEV_UPDATER` set, which is what puts a development build on the
release-build update path where the crash lives.
#### Motivation for adding to Mudlet
Development builds skip the branch entirely, so the crash only appears
in PTB and release builds - where opening settings is a normal thing to
do.
#### Other info (issues closed, discussion etc)
It took out today's PTB: both macOS jobs segfaulted in
`DialogTeardownTest`, which failed the Linux/macOS build workflow, so
`create-github-release` never ran for it and [the
release](https://github.com/Mudlet/Mudlet/releases/tag/Mudlet-4.22.0-ptb-2026-08-04-295b0002)
got only the Windows installer - the Linux AppImage had built fine.
**Test case:** the macOS CI jobs here are the proof - they crash without
this change. Locally: `ctest -R DialogTeardownTest`.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- New `utils::disconnectChildSignals()`, called by the destructors of
the connection dialog, the preferences and the editor: a window stops
listening to its own widgets before it goes away.
- Covers the reported case (connection dialog `Profile name` field) plus
the same exposure found in the preferences (MMCP chat name, shortcut
editors) and the editor (item name, command, pattern and sound file
fields). The preferences and the editor had no destructor at all before
this.
- New `DialogTeardownTest` covering all three windows, plus a canary
that fails if a future Qt stops emitting the focus-out signals the whole
thing rests on.
#### Motivation for adding to Mudlet
Destroying one of these windows while the text cursor sits in one of its
fields aborts the run - which is how #9574 turned up, in a functional
test - and the windows should simply be safe to destroy, rather than
safe only along the `close()` paths that happen to hide them first.
#### Other info (issues closed, discussion etc)
Closes#9574.
The mechanism: a visible window is taken off the screen while its
base-class destructors unwind (`~QDialog` hides it, `~QWidget` closes
any other window class). That moves the keyboard focus off the field
holding it, the field reports `editingFinished()`, and Qt delivers that
to a slot of an object whose derived part is already gone:
```
ASSERT failure in dlgConnectionProfiles: "Called object is not of the correct type (class destructor may have already run)"
```
**How much of this can a player hit today: as far as I can trace, none
of it**, which is why there are no crash reports behind this:
- Every production teardown goes through `close()` / `accept()` /
`reject()` first, and that hide happens while the object is still whole
- so the field's `editingFinished()` is delivered normally and the edit
is saved, exactly as before. `Host::closeChildren()` closes the editor
that way, `mudlet::closeEvent()` closes the connection dialog that way.
- Nothing `delete`s or `deleteLater()`s these three windows directly.
- At exit `main()` deletes the QApplication, which destroys platform
windows without running widget destructors, so the preferences dialog -
the one window nothing explicitly closes - is never destructed either.
- The assert is a `Q_ASSERT_X`, and since we never set
`CMAKE_BUILD_TYPE`, Qt defines `QT_NO_DEBUG` for our builds and compiles
it out. A shipped build would not abort at that point; it would run the
slot against destroyed members instead, which is undefined behaviour
that can quietly rename a profile or a trigger.
So this is a latent trap rather than a live player crash: it fires today
in the test suite, and it fires the moment any future code destroys one
of these windows while it is on screen. The fix is small enough to be
worth taking on those terms.
Verified with standalone Qt probes: `QLineEdit` emits once anything has
written to it (`setText()` is enough, even with an empty string),
`QAbstractSpinBox` and `QKeySequenceEdit` emit unconditionally, and
plain child widgets such as the editor's `dlg*MainArea` panels are not
exposed - their slots still run while they are alive.
`test/functional_tests/DialogTeardownTest.cpp` is formatted with the
repo's clang-format, which the older tests next to it predate.
**Test case:** `ctest -R DialogTeardownTest`. All three cases abort on
`development` with the assert above and pass here. There is no manual
GUI reproduction - see the tracing above.