When a QML Button inside a Repeater delegate calls backend.unloadUiModule()
or root.launchUIModule(), the backend emits uiModulesChanged / launcherAppsChanged
synchronously. This causes QQuickRepeater::setModel to fire, which calls
clear() → setParentItem(nullptr) on every delegate including the one whose
click handler is still on the call stack. QQuickItemPrivate::derefWindow then
crashes trying to walk the button's child item tree while the window pointer
on one of those children is null (SIGSEGV, EXC_BAD_ACCESS at 0x0).
Two fixes:
1. UIPluginManager::unloadUiModule — extract body into unloadUiModuleImpl
and wrap the normal (non-shutdown) path in QMetaObject::invokeMethod with
Qt::QueuedConnection, matching the existing pattern in loadCoreModule /
unloadCoreModule. confirmUnloadCascade already defers via QueuedConnection
so its internal call is updated to call unloadUiModuleImpl directly.
2. MainContainer — change the SidebarPanel.launchUIModule → onAppLauncherClicked
connection from AutoConnection to QueuedConnection. setCurrentVisibleApp
inside onAppLauncherClicked emits launcherAppsChanged synchronously, which
resets both sidebar Repeaters while the clicked SidebarAppDelegate's
onClicked is on the call stack.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>