Summary
Batch of 9 verified QML UI bugs (null guards, binding/delegate staleness, timer/focus races). All confirmed by re-reading code at commit 4325418e. No fixes applied.
Environment
- Shell commit:
4325418e (caelestia-dots/shell, Sep 2026)
- OS: CachyOS Linux, Hyprland (Wayland)
- Quickshell-based shell at
~/.config/quickshell/caelestia
Bugs
1. StyledProgressBar OOB via operator precedence (high)
- File:
components/controls/StyledProgressBar.qml:164,166
- Code:
manager.activeIndicators[index + 1 % manager.activeIndicators.length], then next.startFraction unguarded.
% binds tighter than +, so this is index + (1 % len). On async shrink/stale delegate next is undefined → throw. Compare guarded pattern at lines 146/152.
2. windowinfo Preview unguarded IPC + divide-by-zero (high)
- File:
modules/windowinfo/Preview.qml:74,92-93
- Code:
root.client?.lastIpcObject.size[0] / root.client?.lastIpcObject.size[1] — ?. guards only client; if lastIpcObject is null it throws. size[1]==0 yields Infinity → corrupt constraintSize.width. Line 93 client.lastIpcObject.at[0] / mon.name assume non-null.
- Trigger: inspected window closes or Hyprland IPC lags while windowinfo open.
3. windowinfo Details ?. stops at client (high)
- File:
modules/windowinfo/Details.qml:51,56,62 (same pattern :26,78,84,89,95,101,106,113)
- Code:
root.client?.lastIpcObject.at[0] ?? -1, ...size[0] ?? -1, root.client?.workspace.name ?? -1.
- If
client is non-null but lastIpcObject/workspace is null, .at/.size/.name throws before ?? runs.
- Trigger: window close / workspace teardown while details visible.
4. BatteryMonitor hibernate timer never cancelled (high)
- File:
modules/BatteryMonitor.qml:31-34,78-83
hibernateTimer (5s single-shot → SessionManager.hibernate()) is start()ed at/below critical but stop() appears nowhere in repo. Early-return on !onBattery and onReady/onPercentage don't cancel it.
- Trigger: battery dips to critical then recovers (charger plugged in within 5s) — machine still hibernates.
5. Menu parent binding null deref (med, latent)
- File:
components/controls/Menu.qml:36-40
- Code:
const win = QsWindow.window; ... (win as QsWindow).contentItem with no null check.
- Trigger: Menu created in async Loader/drawer before attaching to a window. Current callers live inside
ContentWindow so latent, but robustness bug.
6. FolderContents delegate staleness (med)
- File:
components/filedialog/FolderContents.qml:191-201,217
- Icon
source and name.text assigned only in Component.onCompleted, never rebound on modelData change. No reuseItems/onModelDataChanged binding.
- Trigger: scrolling large folder, rename/mime change, navigating directories shows wrong icon/filename.
7. Wallpaper double-create + error-path leak (med)
- File:
modules/background/Wallpaper.qml:18-35,127-131
onSourceChanged creates synchronously at startup (initial source fires it) and onCompleted schedules a second createObject via Qt.callLater. Old-image Timer only destroys when root.current.status === Image.Ready, so broken path → blank/error image current, old one leaked. Rapid source changes pile up.
8. Launcher/session focus race (med)
- File:
modules/launcher/Content.qml:109-123, modules/session/Content.qml:30-39
forceActiveFocus() in Component.onCompleted fires before drawer window visible/active (async loader + animation), so type-ahead goes nowhere until manual click. onSessionChanged/onLauncherChanged handlers ping-pong focus with no visible/active guard. Panels.qml:88,99 instantiates both always.
9. Areapicker unguarded destructuring + sticky state (med)
- File:
modules/areapicker/Picker.qml:57-60,87,198-201
let {at:[cx,cy], size:[cw,ch]} = client.lastIpcObject after only if (!client) continue — throws if lastIpcObject/at/size null, aborting loop. JSON.parse(hyprctl cursorpos) uncaught. checkClientRects only sets onClient=true, never resets on miss → stale sx/sy/ex/ey.
- Trigger: transient windows while picker open,
hyprctl errors.
Expected / Actual
Expected: guards on IPC nulls, timer cancellation, bindings instead of onCompleted assignment, focus after visible.
Actual: throws/stale UI/spurious hibernate under the triggers above.
Additional Context
Found by systematic review, each re-verified by a second pass reading the exact lines. Happy to split into separate issues or open PRs per fix.
Summary
Batch of 9 verified QML UI bugs (null guards, binding/delegate staleness, timer/focus races). All confirmed by re-reading code at commit
4325418e. No fixes applied.Environment
4325418e(caelestia-dots/shell, Sep 2026)~/.config/quickshell/caelestiaBugs
1. StyledProgressBar OOB via operator precedence (high)
components/controls/StyledProgressBar.qml:164,166manager.activeIndicators[index + 1 % manager.activeIndicators.length], thennext.startFractionunguarded.%binds tighter than+, so this isindex + (1 % len). On async shrink/stale delegatenextisundefined→ throw. Compare guarded pattern at lines 146/152.2. windowinfo Preview unguarded IPC + divide-by-zero (high)
modules/windowinfo/Preview.qml:74,92-93root.client?.lastIpcObject.size[0] / root.client?.lastIpcObject.size[1]—?.guards onlyclient; iflastIpcObjectis null it throws.size[1]==0yieldsInfinity→ corruptconstraintSize.width. Line 93client.lastIpcObject.at[0]/mon.nameassume non-null.3. windowinfo Details
?.stops at client (high)modules/windowinfo/Details.qml:51,56,62(same pattern :26,78,84,89,95,101,106,113)root.client?.lastIpcObject.at[0] ?? -1,...size[0] ?? -1,root.client?.workspace.name ?? -1.clientis non-null butlastIpcObject/workspaceis null,.at/.size/.namethrows before??runs.4. BatteryMonitor hibernate timer never cancelled (high)
modules/BatteryMonitor.qml:31-34,78-83hibernateTimer(5s single-shot →SessionManager.hibernate()) isstart()ed at/below critical butstop()appears nowhere in repo. Early-return on!onBatteryandonReady/onPercentagedon't cancel it.5. Menu parent binding null deref (med, latent)
components/controls/Menu.qml:36-40const win = QsWindow.window; ... (win as QsWindow).contentItemwith no null check.ContentWindowso latent, but robustness bug.6. FolderContents delegate staleness (med)
components/filedialog/FolderContents.qml:191-201,217sourceandname.textassigned only inComponent.onCompleted, never rebound onmodelDatachange. NoreuseItems/onModelDataChangedbinding.7. Wallpaper double-create + error-path leak (med)
modules/background/Wallpaper.qml:18-35,127-131onSourceChangedcreates synchronously at startup (initialsourcefires it) andonCompletedschedules a secondcreateObjectviaQt.callLater. Old-imageTimeronly destroys whenroot.current.status === Image.Ready, so broken path → blank/error image current, old one leaked. Rapidsourcechanges pile up.8. Launcher/session focus race (med)
modules/launcher/Content.qml:109-123,modules/session/Content.qml:30-39forceActiveFocus()inComponent.onCompletedfires before drawer window visible/active (async loader + animation), so type-ahead goes nowhere until manual click.onSessionChanged/onLauncherChangedhandlers ping-pong focus with novisible/activeguard.Panels.qml:88,99instantiates both always.9. Areapicker unguarded destructuring + sticky state (med)
modules/areapicker/Picker.qml:57-60,87,198-201let {at:[cx,cy], size:[cw,ch]} = client.lastIpcObjectafter onlyif (!client) continue— throws iflastIpcObject/at/sizenull, aborting loop.JSON.parse(hyprctl cursorpos)uncaught.checkClientRectsonly setsonClient=true, never resets on miss → stalesx/sy/ex/ey.hyprctlerrors.Expected / Actual
Expected: guards on IPC nulls, timer cancellation, bindings instead of onCompleted assignment, focus after visible.
Actual: throws/stale UI/spurious hibernate under the triggers above.
Additional Context
Found by systematic review, each re-verified by a second pass reading the exact lines. Happy to split into separate issues or open PRs per fix.