fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection#159
fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection#159git-napkin wants to merge 83 commits into
Conversation
Add explicit sourceSize to avatar images in notch, lock screen, and resource monitor to prevent downscaling and use full available pixels.
…p polish - OSD: fix hover bug (was instantly hiding on enter), add slide+fade animation, wider pill (240x56), % suffix on value, 3s timer, font sizes via Styling.fontSize() - NotchAnimationBehavior: add subtle Y-translate drop-in, tighten scale floor from 0.8 to 0.85 - LockScreen: add animated date label below clock digits - StyledToolTip: fix desciription typo, reduce hover delay 1000→700ms, dim description text to 70% opacity, use Styling.fontSize() - SysTrayItem: fix desciription→description to match tooltip fix
- BarContent: remove redundant !== undefined checks - PanelTitlebar: use customContent array instead of children at init
- Add QML unit test suite (ConfigValidator, theme defaults, error patterns) - Create ErrorHandler singleton for centralized error tracking - Add SafeLoader component with error handling and fallback UI - Update AGENTS.md with testing docs and error handling anti-pattern - Integrate ErrorHandler into shell startup
… internal - setFallback() now actually parents the item into fallbackContainer so it renders when hasError is true (was previously orphaned) - Remove public 'loader' property alias — exposes internal Loader as API, allowing callers to bypass root.sourceComponent and write directly to internalLoader.sourceComponent - Make internalLoader fully internal (id only, no alias) - isLoading/hasError/isReady now bind to internalLoader directly - retry() null-cycles internalLoader.sourceComponent, not loader alias - Add newline at EOF lalala
Previously 'visible: GlobalStates.osdVisible' would hide the PanelWindow the moment osdVisible flipped false, killing the fade-out and slide-down animations before they could play. Fix: bind window visibility to 'GlobalStates.osdVisible || osdRect.opacity > 0' so the window stays alive until the opacity Behavior finishes animating to 0, then naturally goes invisible. Both the slide and opacity exit animations now play fully before the window is culled.
The previous check used 'command -v vulkaninfo || command -v glxinfo', meaning any system with glxinfo (OpenGL/GLX) would get QT_QUICK_BACKEND=vulkan even without a Vulkan ICD installed. Qt silently falls back to software rendering in that case, which is worse than just using opengl. Use 'vulkaninfo --summary' instead, which actually enumerates Vulkan ICDs and exits non-zero when none are found.
vulkan-headers is a development package (C headers for writing Vulkan applications). End users only need the runtime loader (vulkan-icd-loader) plus a GPU-specific ICD (vulkan-radeon, vulkan-intel, etc.) which they should already have from their mesa/nvidia installation. The Fedora list correctly uses only vulkan-loader; the Arch list now matches the same intent with vulkan-icd-loader only.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f489476788
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| StyledRect { | ||
| id: placeholder | ||
| anchors.fill: parent | ||
| visible: root.showPlaceholder && (root.isLoading || root.hasError) && !root.fallbackItem |
There was a problem hiding this comment.
Provide a variant for SafeLoader's StyledRect placeholder
StyledRect requires its variant property, but this placeholder instance never sets one. As soon as SafeLoader is instantiated, QML will raise a required-property initialization error for this child and the fallback/loading UI cannot be created reliably. Assign a valid variant (for example "common" or "popup") on this StyledRect instance.
Useful? React with 👍 / 👎.
| internalLoader.sourceComponent = null; | ||
| internalLoader.sourceComponent = current; |
There was a problem hiding this comment.
Keep sourceComponent binding intact in retry()
internalLoader.sourceComponent starts as a binding to root.sourceComponent, but retry() overwrites it imperatively (null then current). In QML, that assignment removes the original binding, so after the first retry later updates to root.sourceComponent no longer propagate to the loader. This can leave SafeLoader showing stale content when callers swap components after a retry.
Useful? React with 👍 / 👎.
Use a queue (_pendingTimeoutIds) instead of a single overwritable property to ensure all timed-out notifications are properly cleaned up when multiple timeouts occur in rapid succession.
- Quote font-family values so multi-word names parse correctly in CSS - Reduce dark-mode text scaling factors (1.05-1.40 instead of 1.15-3.19) to keep all text variants visible on dark backgrounds - Increase light-mode scaling factors (1.5-5.0 instead of 1.15-3.19) to create proper visual hierarchy when fg is near-black
- fix(Ai): remove dead re-entrancy guard and destroy rejected duplicate models (memory leak) - fix(GameModeService): route state through StateService instead of writing states.json directly (race fix) - fix(pauseAutoSave): sync across theme/shell/compositor edit sessions + discard-on-close safety net - fix(null-safety): guard focusedMonitor, note title in delete mode, clipboard model access - fix(config): align 7 adapter defaults with defaults/*.js and add missing compositor.layout - perf(Config): debounce auto-saves through a 150ms timer instead of per-property disk writes - perf(WavyLine): wire CarouselProgress active->running to stop idle 60fps canvas redraws - perf(lists): reuseItems + larger cacheBuffer on Clipboard/Tmux/Notes ListViews - perf(IdleMonitor): gate media-inhibitor poll to isIdle - chore: StateService init via Component.onCompleted; quiet PowerProfile/Wallpaper debug logs
- add modules/widgets/dashboard/list_utils.js with ROW_HEIGHT, expandedRowHeight, and scrollToReveal shared across Clipboard/Notes/Tmux tabs - replace triplicated adjustScrollForExpandedItem scroll math with the helper - add lastworkingcommit.txt (rollback point 3bb8f3e)
Convert every Easing.OutBack (Back-easing overshoot) to Easing.OutQuart across the shell: presets, overview, defaultview, compact player, full player, dashboard, binds/system panels, wallpapers, notifications, and lockscreen. Drop the now-dead easing.overshoot lines. Notch animations were already converted in the prior commit.
Wrap SettingsTab in an asynchronous Loader inside SettingsWindow so the heavy SettingsTab component (and its 500ms-later full-panel search indexer that crawls ShellPanel/BindsPanel/etc.) is incubated on a later tick instead of synchronously at window-open time. Previously this blocked the main thread mid-way through the concurrent notch close animation, causing a ~1s freeze.
The settings search indexer previously instantiated ALL 10 heavy panels (ShellPanel 1913 lines, BindsPanel 1974, …) back-to-back the instant Settings opened, blocking the UI thread mid-way through the concurrent notch close (a ~1s freeze). - Replace the eager Qt Loader indexer with Quickshell's LazyLoader, which creates each panel in the gaps between frame renders instead of blocking. - Defer indexing start by 1500ms (after the window/open animation settles) so opening Settings is instant and the notch close stays smooth. - Crawl the active panel immediately via panelLoader.onLoaded (zero extra cost, since it is already instantiated) so search stays progressive. - SettingsIndex.staticItems already covers the full index, so search works even before background crawling completes. Per Quickshell docs: LazyLoader 'works on creating the component in the gaps between frame rendering to prevent blocking the interface thread.'
Opening Settings created a new toplevel FloatingWindow while the notch was still mid-close. The heavy window + SettingsTab instantiation ran on the shell's main thread and stalled the notch's close animation (~1s freeze). Gate settingsWindowLoader behind a 400ms timer (animDuration is 300ms) so the window is only created after the notch has fully closed. The notch close now runs unobstructed; the Settings window appears just after it settles.
Stability: - GlobalShortcuts: restart IPC pipe listener on abnormal exit; guard Config.prefix - NetworkService: auto-restart nmcli monitor subscriber on exit - Brightness: null-guard AxctlService.focusedMonitor in inc/dec - ConfigValidator: stop dropping unknown keys (fixes ai.customEndpoint loss) - ai.js: add customEndpoint/customCurlTemplate defaults - cli.sh: bound FIFO write with timeout (no hung shell on reader-less pipe) - GlobalStates: seed assistant size/position after config fully loads Performance: - AxctlService: O(n) window map via address index (was O(n^2) per event) - Notifications: debounce full-list serialize+write (300ms) - ClipboardTab: drop per-link 1ms polling Timer (seed favicon on completed) Consistency: - Styling: add popupRadius(), tint(), hover/press alpha, animEasing helpers - OSD/NotificationDelegate/Dock/Tooltip: use Styling helpers, scope OSD click area - BarPopup: remove debug console.log on every popup open Customizability: - SystemPanel: expose dashboardPersistTabs / dashboardMaxPersistentTabs
Security: - Replace eval in wf-record.sh with array-based command execution - Replace XOR cipher in keystore.py with PBKDF2-HMAC-SHA256 + Fernet - Remove insecure XOR fallback (require cryptography as hard dependency) - Add python3Packages.cryptography and pytest to Nix packaging UX smoothness: - Add line_buffering to fprintd_auth.py for real-time output - Add flush=True to link_preview.py - Remove unnecessary Qt.callLater in PowerProfileSelector.qml - Bump config auto-save debounce from 150ms to 250ms - Add startup init logging to shell.qml - Make weather.sh cache TTL configurable via Config.weather.cacheTtl Config consistency: - Fix workspaceSpacing mismatch (4 -> 8 to match defaults) - Add .pragma library to defaults/ai.js - Add config migration system with version tracking - Add enum and range validators to ConfigValidator.js - Add JSDoc comments to config defaults Theme consistency: - Replace hardcoded hex colors in Colors.qml with adapter.* properties - Fix empty power icon in Icons.qml - Replace font.pixelSize with Styling.fontSize() across 18 bar files - Replace Easing.OutCubic/OutQuart with Styling.animEasing across bar modules - Replace manual Qt.rgba() with Styling.tint() in 3 QML files Code quality: - Add set -euo pipefail to 10 bash scripts - Replace 14 bare except: with except Exception: - Add __main__ guard to colorpicker.py - Add argparse to system_monitor.py and link_preview.py - Add cache TTL and error handling to weather.sh - Add error handling to ocr.sh Nix packaging: - Fix flake URI in install.sh - Add wayland.enable dependency - Add meta attributes to package and NixOS module - Create shell.nix Documentation: - Update root AGENTS.md with missing modules - Create AGENTS.md for dock, notifications, widgets modules Testing & CI: - Add unit tests for keystore, system_monitor, colorpicker - Add GitHub Actions CI pipeline (nix build, python tests, shellcheck) CLI: - Optimize find_ambxst_pid with single pgrep call - Add ps aux fallback for systems without pgrep
…t child declaration error
…id QML QtObject child declaration error
The axctl daemon has a timing issue: config set commands sent before the daemon is ready are lost, and the daemon may re-read its TOML file after config set, reverting the change. Switch to hyprctl keyword, which is synchronous and avoids the daemon IPC entirely. Also removed stale result symlink and added to .gitignore.
The fork has the critical DefaultOutputPath fix (+ in dir name) and Config.Set now re-applies TOML so generated files stay in sync.
The visualContent Item had layer.enabled: true with a Shadow effect, which rendered the entire shell panel as a single opaque texture (a: 1 per hyprctl layers). The compositor couldn't blur behind the notch menus because it saw zero transparency on the overlay surface. Each component (bar, dock, notch) already has its own shadow/border handling, so the unified layer is redundant. Removing it lets Qt render directly to the PanelWindow surface, preserving transparency and allowing Hyprland's layer-rule blur to work.
// psst, this description was made by ai, theres too many tiny changes for me to bother with looking through the entire diff thingy for things i would probably forget about in a description, since this was written over the span of a couple weeks or so
Describe your changes
PanelWindow.visibletoosdVisible || osdRect.opacity > 0. Added slide-in/out translate animation, tweaked sizing and margins, fixed hover behavior so hovering pauses the hide timer instead of immediately dismissing, and fixed the percentage display to clamp against-0%.modules/components/SafeLoader.qmlcomponent — a drop-inLoaderwrapper with error handling, fallback UI, and a retry function using the standard null-cycle pattern.vulkaninfo --summary(more reliable thanglxinfo), plusQML_USE_TYPED_ARRAYSandQT_THREADED_RENDERERenv vars for rendering performance.vulkan-loader/vulkan-icd-loaderto dependency lists for Fedora and Arch respectively.desciription→descriptionin bothStyledToolTip.qmlandSysTrayItem.qml.font.pixelSizevalues in OSD and tooltip to useStyling.fontSize()for consistency.sourceSizehints to avatar images inMetricsTab,LockScreen, andUserInfoto avoid loading full-res images into GPU memory unnecessarily.PanelTitlebarcustom content visibility binding, made some container backgrounds transparent where a colored background was leaking through, cleaned up a few redundantimplicitWidth !== undefinedchecks inBarContent.qml.Add screenshots
Skipping screenshots, most changes are either invisible infrastructure, minor metric tweaks, or animation timing adjustments that don't photograph well.
Does this change any existing behavior?
Yes, a few things behave differently:
StyledToolTip'sdesciriptionproperty is renamed todescription— any callers using the old misspelled name will need to update.