Skip to content

Quattro - #6231

Draft
dhh wants to merge 1674 commits into
masterfrom
quattro
Draft

Quattro#6231
dhh wants to merge 1674 commits into
masterfrom
quattro

Conversation

@dhh

@dhh dhh commented Jul 17, 2026

Copy link
Copy Markdown
Member

Quattro Beta

Omarchy 4 aka Quattro is the biggest release since the project started. The entire desktop shell has been reimagined in Quickshell: the bar, launcher, menus, notifications, on-screen displays, control panels, lock screen, and polkit agent now all live inside a single long-running shell process with a plugin architecture. That means Waybar, Walker, Mako, SwayOSD, hyprlock, hypridle, swaybg, and polkit-gnome are all gone, replaced by one coherent, fully-themed, IPC-scriptable shell.

You can upgrade an existing Omarchy installation to Quattro with the following command:

curl -fsSL https://omarchy.org/upgrade-to-quattro-dev | bash

This will put you on the beta development version. So not something that's recommended for production systems. Ensure you have a good backup first!

Better yet, install it on a secondary machine using the ISO: https://iso.omarchy.org/omarchy-quattro-beta3.iso

Headline Features

  • Reimagine the entire desktop shell in Quickshell.
  • Move Omarchy internals from git to system packages.
  • Offer dual-boot installation when free space is available.
  • Shrink the ISO by over a gigabyte.
  • Speed-up installation by +30%.
  • Convert all Hyprland configs to lua for full 0.56 compatibility.
  • Expand theme coloring from base 8 to 24 so btop/nvim/vscode themes can be autogenerated.
  • Use networkmanager for new network panel for greater compatibility.
  • Add nested search to Omarchy menu and merge the launcher into that one unified menu (Super + Space).
  • Add text scaling across the shell, GTK, and terminals in one move via the Display panel.
  • Add speaker tunings for 2026 XPS 14/16 laptops for dramatically better sound.
  • Add menu bar repositioning (top/bottom/left/right) and transparency.
  • Add menu bar panels for Tailscale + Dropbox.
  • Add weather panel with configurable location.
  • Add Omawrite, Omacut, and Omacalc as default applications.
  • Add a configurable default coding agent with instant launch shortcuts.
  • Add Solitude, Last Horizon, and Lupine as new themes.
  • Add window position saving per workspace for quick layout restoring.
  • Add much-improve clamshell operations for laptops used with external monitors.
  • Add deferred first-boot provisioning and factory reset, so a machine can be installed for someone else and handed over untouched.

The Shell

  • Rewrite the entire desktop shell in Quickshell — bar, launcher, menus, notifications, OSDs, panels, lock screen, and polkit agent as plugins in one process by @dhh and @ryanrhughes
  • Add a new modular bar with interactive widgets: workspaces, active window, clock (click for ISO week format), weather, media (MPRIS), system tray, battery, keyboard layout, microphone, update indicator, and manual-state indicators (DND, night light, stay awake, screen recording, dictation, reminders)
  • Move the bar by dragging it to any screen edge — a ghost slab previews the target edge while you drag — and double-click empty bar space to toggle transparency (you can also set this via Setup > Menu Bar in the Omarchy menu)
  • Add a bar plugin system: third-party widgets and even whole replacement bars installed straight from git with omarchy plugin add <git-url>, and managed from Setup > Plugins (add, clone, enable, disable, remove) by @ryanrhughes and @dhh
  • Add a native launcher with fuzzy/acronym matching, live app icon indexing, and hidden-entry management — now merged into the Omarchy menu itself, so SUPER + SPACE searches apps and commands from one surface
  • Add a dedicated apps-only menu on SUPER + ALT + SPACE
  • Add a native notification daemon with popups, a history center with pending/past tabs (SUPER + SHIFT + ALT + ,), do-not-disturb, and deduping — popups on screen survive shell restarts (like the one every update performs), so critical alerts always make it across
  • Dismiss notification popups with a right-click by @taxin-404
  • Add a native clipboard manager with image previews and sensitive-content exclusion (SUPER + CTRL + V)
  • Add a native emoji picker (SUPER + CTRL + E) replacing Walker picker.
  • Add native volume/brightness/media OSDs replacing swayosd.
  • Add control panels for Audio (SUPER + CTRL + A), Bluetooth (SUPER + CTRL + B), Network (SUPER + CTRL + W), Display (SUPER + CTRL + D), and Power (SUPER + CTRL + P) replacing bluetui, impala.
  • Add optional service widgets with full panels for Tailscale (connection control, exit-node picker with Mullvad nodes grouped by country) and Dropbox (login, storage, recent files) by @ryanrhughes and @dhh
  • Add a model-usage bar widget showing Claude Code/Codex usage stats, optionally synced across machines by @ryanrhughes
  • Add a Google Meet picture-in-picture widget by @ryanrhughes
  • Offer 12-hour AM/PM clock formats when right-clicking the bar clock by @scottjones
  • Add a battery percentage readout toggle to the power widget
  • Show the OSD and use touchpad-sized steps when scrolling volume and brightness on bar widgets by @heyssh
  • Replace hyprlock with shell-powered password and fingerprint PAM flows, and invite you to enroll a finger on first run when a reader is present — with fingerprint offers on the lock screen, polkit, and sudo gated by lid state
  • Move the Omarchy menu into the shell as a filterable, nested command palette defined in JSONC (extensible via ~/.config/omarchy/extensions/omarchy-menu.jsonc)
  • Make the shell event-driven rather than polled: status indicators, monitor and network state, and the background all react to signals now, so an idle desktop stops burning CPU
  • Add a Fireworks prepaid-balance view to the coding-agent usage panel, and count Claude/Codex use from Pi, Oh My Pi, and OpenCode sessions too by @dhh
  • Expose the battery percentage toggle in the Omarchy menu by @bwynnyck
  • Open the bar's right-side panels with SUPER + CTRL + 1 through 9, counted left to right — rearranging the section or adding a widget renumbers them with no binding to rewrite by @dhh
  • Ship the keyboard layout widget on the bar by default: hidden while only one layout is configured, click to cycle, and labeled with the xkb language code (EN, PT, AR) instead of a truncated description by @dhh and @defer
  • Keep the bar mapped while hidden so revealing it is a margin change rather than a rebuild — 10-14ms in both directions, against 155-595ms before by @vivek7405
  • Show the per-model weekly limits Claude's usage endpoint reports, so a window like Fable's own allowance stops being spent against invisibly by @GebaRoanoke

Theming & Aesthetics

  • Add a visual theme switcher: a filterable carousel of live theme previews (SUPER + SHIFT + CTRL + SPACE) by @dhh
  • Add a visual background switcher in the same style (SUPER + CTRL + SPACE) by @dhh
  • Add a semantic theme color system with normalized color names by @bjarneo
  • Add generation of neovim, VS Code, and btop configs straight from a theme's expanded colorset, so themes carry fewer hand-maintained files by @bjarneo, @ryanrhughes
  • Sync the active Omarchy theme to Claude Code by @MoizIbnYousaf
  • Add ~/.config/omarchy/shell.toml as a machine-level override merged over the active theme, so a personal font, spacing, or bar tweak survives theme switching — and it's watched, so edits re-flow the shell live
  • Add Last Horizon and Solitude themes by @HANCORE-linux
  • Bring back the classic Omakub background in Tokyo Night for early-Linux-adoption nostalgia
  • Add Lupine theme by @bjarneo
  • Add a Pi theme based on the system theme by @ryanrhughes
  • Add integrated corner controls (rounded/sharp) applied across Hyprland, notifications, lock screen, and menus
  • Add omarchy display text size: one knob, 9–20px, that moves the shell font, GTK's text-scaling-factor, and the terminal point size in lockstep, with a notched slider in the display panel
  • Add Tab/Shift+Tab navigation in the theme and background switchers by @bjarneo
  • Generate the theme and background picker thumbnails with libvips across every core: a 92-wallpaper directory drops from 14.2s to 1.3s on a cold cache, and the bundled theme previews from 2.0s to 0.26s by @dhh
  • Retune opacity for Hyprland 0.56's corrected alpha premultiplication, and parse plain hyprctl binds output to survive its broken JSON by @dhh

Apps & Defaults

  • Add Omawrite, a dead-simple Markdown writing app, as the default writing tool replacing Typora (SUPER + SHIFT + W) by @dhh
  • Add Omacut, a dead-simple video length trimmer built on ffmpeg by @dhh
  • Add Omacalc, a dead-simple calculator replacing GNOME Calculator (SUPER + CTRL + Q) by @dhh
  • Add a configurable default coding agent: pick Claude Code, Codex, OpenCode, Pi, Oh My Pi, Gemini, Grok, Copilot, or Crush under Setup > Defaults > Agent, then launch it with SUPER + SHIFT + CTRL + A or the a terminal alias — lazy-installed on first use by @dhh
  • Switch the default terminal to Foot for better resource utilization by @dhh
  • Add a yt-dlp-powered "Download Video" Chromium extension (on any URL with a video press Alt + Shift + D by @dhh
  • Switch the default image annotator from Satty to Tensaku
  • Add Moonlight client for Sunshine game/desktop streaming as new default by @dhh
  • Add automounting of removable drives via udiskie by @ryanrhughes
  • Add mpv MPRIS support so media keys control mpv by @berenddeboer
  • Use dua for disk usage by @crmne and @dhh
  • Make WhatsApp Web follow the system light/dark theme, and collapse it to a Signal-style avatar rail in slim windows by @scottjones and @dhh
  • Move Signal, Spotify, and 1Password to on-demand installs and remove Figma + GitHub from the defaults by @dhh
  • Switch to the lighter basic JetBrains Mono Nerd font to save 200mb on install by @dhh

Capture

  • Add relatively sized preview to the live webcam overlay on screen recordings (and offer further tweaks via SUPER + ALT + [ / ] by @dhh (on request from @jasonfried)
  • Drive the region picker from the keyboard: RETURN captures the highlighted window, CTRL + RETURN the whole display, and TAB and the arrow keys move the selection through the workspace's windows by @jzimdars and @dhh
  • Add QR code capture: select a region and the QR code inside it decodes straight to the clipboard, so an otpauth:// setup code on screen no longer needs a phone. The decoded value never touches disk and is marked sensitive, so clipboard history skips it by @dhh and @Wondertan
  • Handle rotated monitors in the recording region picker.
  • Trash imv deletions and add Ctrl+E to edit in Tensaku by @jondkinney

Controls

  • Convert the Hyprland configuration to Lua: bindings, monitors, and toggles are now expressive Lua (o.bind("SUPER + SHIFT + W", "Omawrite", { launch = "omawrite" })) by @ryanrhughes
  • Add save/restore of window widths per app and workspace on SUPER + ALT + HOME / SUPER + HOME by @dhh
  • Add external monitor brightness via DDC/CI: the brightness keys and OSD now control the focused external display, while laptop panels keep using the kernel backlight by @ssupt
  • Add fine (±25px) and coarse (±300px) window resizing tiers by @zaborowskimichal and @dhh
  • Add alternative media next/previous bindings for keyboards with only a play button by @dhh
  • Add a tmux keybindings viewer (SUPER + ALT + K) and new-pane/split bindings for tmux, kitty, ghostty, and alacritty by @ryanrhughes, @vtmx, @heymaikol, and @dhh
  • Add a tmux attention indicator: when a pane flags itself — or produces output while its terminal is unfocused — the bar lights up and SUPER + CTRL + J jumps straight to it. Tmux hooks push the state, so nothing polls while nothing is waiting by @dhh
  • Add laptop clamshell handling with idempotent scale recovery, internal-display toggle, and display mirroring by @dhh
  • Toggle Caps Lock by pressing both Shift keys
  • Ship Herdr alongside tmux with matching keybindings, configuration, and hdl/hds/hdlm/hsl development-layout helpers by @dhh
  • Open Herdr with SUPER + CTRL + RETURN and its keybindings viewer with SUPER + CTRL + K, mirroring the tmux pair — plus tmux's tab moves, zoom flag, hostname in the window title, and hidden outer pane frame carried over by @dhh
  • Clean up the terminal and reconnect when an SSH connection drops: a remote tmux, herdr, or editor that dies with the link no longer leaves mouse tracking and the alternate screen armed on your local terminal, and an established interactive session comes back on its own. Client keepalives make the drop show up in about 45 seconds instead of whenever TCP gives up by @dhh

Networking/Connections

  • Add ping, live up/down stats, speed test, and DNS provider selection to the network panel by @dhh
  • Add Wi-Fi QR sharing to the network panel, so another device can join with a scan by @tobi
  • Add a Wi-Fi band toggle to the network panel by @dhh
  • Add per-device Bluetooth pairing, connection state, and forget/disconnect flows by @dhh
  • Add audio output/source switching that preserves playback, with recovery when audio services get stuck by @dhh
  • Switch Wi-Fi management to NetworkManager, replacing iwd/impala/bluetui/wiremix with the shell panels by @dhh
  • Connect to enterprise (802.1X) Wi-Fi networks from the network panel

System & Upgrade

  • Re-architect Omarchy into proper Arch packages with system files shipped via /etc, so updates flow through pacman by @ryanrhughes
  • Add an ALPM guard that routes system updates through omarchy update (bypassable via OMARCHY_ALLOW_DIRECT_PACMAN=1) by @ryanrhughes
  • Switch privilege escalation to pkexec/polkit with a themed prompt that shows exactly what's being authorized by @dhh
  • Switch lazy-loaded tools (Claude Code, GitHub CLI) from npm to mise, and keep their wrappers plus omarchy update on current releases instead of waiting out the release cooldown by @dhh
  • Launch apps in their own systemd scopes instead of the compositor's cgroup, and let systemd-oomd take out a runaway app instead of losing the whole session by @defer and @dhh
  • Warn when disk space is low before updating by @ericvrp
  • Add pre-refresh-pacman.d hooks for custom repository support by @tyvsmith
  • Persist Bluetooth power state across reboots, by making an rfkill soft block the state systemd restores at boot rather than forcing the adapter off every time, and carrying existing installs across by @heymaikol and @dhh
  • Enable Docker multi-arch builds by default by @axelfontaine
  • Add weather locations that can be pinned to a chosen place instead of following IP geolocation by @dhh
  • Add SSHD setup and removal under Setup/Remove > Security by @dhh
  • Ship NordVPN directly from the Omarchy package repository by @dhh
  • Add per-laptop speaker tunings: a PipeWire filter chain in front of the internal speaker sink, matched by DMI string, restoring the voicing the vendor's Windows DSP does and Linux doesn't. Shipping for the XPS 14 and 16; adding a machine is two data files and no new code by @dhh and @spencerbull
  • Tune swap on zram instead of leaving it at kernel defaults, so large machines stop reaching for the hibernation swapfile early by @dhh
  • Remember explicit power profile choices per power source (AC/battery) across reboots by @dhh
  • Stop blocking the desktop on network-online at boot by @dhh
  • Pin Chromium-based browsers to the gnome-libsecret password store, so backend autodetection can't silently log you out of everything by @nt1998
  • Add a reproducible read/write disk speed test under Trigger > Speed Test, sharing the live dial interface with the network test by @dhh
  • Harden the Quattro upgrade by preserving and verifying boot-critical kernel parameters, forcing a fresh package database before keyring installation, surfacing partial-transition failures, and keeping the NetworkManager/iwd handoff safe by @tmn73 and @dhh
  • Report when an update proceeds without the Snapper snapshot it expected, instead of claiming a snapshot that was never created, by @nille
  • Document the optional Arch debug-symbol repositories directly in the edge pacman config by @flavorjones
  • Add deferred first-boot provisioning: an install can finish with no user at all, and the machine's owner picks their keyboard, account, hostname, and timezone on the first boot instead — behind the same logo, progress bar, and rotating tips the ISO installer uses. On an encrypted disk the LUKS volume is re-keyed from the throwaway install passphrase to the owner's password, all-or-nothing, with every other slot killed by @dhh
  • Add Setup > Reset Computer: swap the running root for a fresh clone of the @factory snapshot the ISO takes at install time, scrub machine identity, accounts, and fingerprint enrollments, and hand the machine back in that untouched first-boot state. A machine with no factory snapshot is turned away with an explanation rather than given a partial wipe by @dhh
  • Extract the account form the ISO installer and first-boot setup share, so an install and the boot that finishes it can no longer offer different keyboard layouts or accept different usernames — with Esc now unwinding to an earlier answer instead of re-asking the same field by @dhh
  • Swap terminaltexteffects for ttfx, a Rust port rendering byte-identical frames as a single dependency-free binary: the screensaver starts in ~1ms instead of ~107ms, holds its frame rate on the heavier effects, and the base image no longer needs Python for it by @dhh
  • Open the keybindings menu on first login rather than describing it in a toast, and wait for NetworkManager to actually settle before deciding you have no Wi-Fi or offering an update by @dhh
  • Build the menu's IPC payload with jq instead of forking perl: opening the menu drops from ~42ms to ~28ms by @dhh

Fixes

  • Fix fontconfig monospace binding overriding app-specific fonts by @Ninso112
  • Fix power profile race on plug/unplug events by @aikazu
  • Fix brightness indicator glitches from hardware key auto-repeat by @mateuszkowalczyk
  • Fix brightness OSD width jitter across percentages by @Pegorim
  • Fix transparent-bar flicker and systray action handling by @kevinmcconnell
  • Fix phantom display scaling by reading the configured scale in clamshell recovery
  • Fix notification expire timeouts being treated as seconds instead of milliseconds
  • Fix new windows — including browser windows and portal file dialogs — opening on the wrong workspace by @scottjones and @dhh
  • Fix the LUKS passphrase prompt ignoring the configured keyboard layout by bundling vconsole.conf in the initramfs (Latin layouts) by @Zeus-Deus
  • Prevent empty disk encryption passwords in the installer by @heymaikol
  • Fix power-profile udev rule wakeup failures from a fixed unit name by @DiegoMirner
  • Prevent the migration notifier from rate-limiting itself into silence by @Pegorim
  • Align snapshot pruning with Snapper's retention (off-by-one) by @Pegorim
  • Wake the internal display only when actually leaving clamshell by @monorkin
  • Force software cursors on nouveau by @stephentaylor-com
  • Install sof-firmware on all Intel SOF audio platforms so more laptops get working sound out of the box by @dhh
  • Fix monitor scale-down getting stuck at 3x due to floating point drift by @dhh
  • Keep software-composited cursors out of screenshots by @dhh
  • Fix new terminals not opening in the current directory on Kitty
  • Fix fip and friends misparsing under zsh by @pkwagner
  • Clear the launcher/menu filter with CTRL + BACKSPACE by @RushiChaganti
  • Only bind F9 to Voxtype when it's installed by @r3quie
  • Prefer Noto Naskh Arabic over Nastaliq Urdu for Arabic text by @husamemadH
  • Run the screensaver on every monitor, not just the last one, by @scottjones
  • Refresh bar widgets on every monitor, not just one
  • Switch Brave Origin from the beta to the stable release by @virtualabishek
  • Fetch high-res site icons for web apps instead of blurry favicons, and decode launcher and tray icons at physical resolution by @dhh
  • Resend the night light temperature until it sticks on a hyprsunset cold start
  • Pin the network panel's speed-test overlay to a fixed light palette so every theme stays legible by @heyssh
  • Fix bg/fg color aliases in theme color resolution by @lukewalker2010
  • Fix silent launch failure for apps whose desktop id ends in .desktop, like Telegram, by @glafeara
  • Fall back to the XDG MIME browser handler when xdg-settings names no browser by @ShiroKSH
  • Show non-ASCII Wi-Fi network names correctly instead of hex escapes by @richardlences
  • Fit the About window to its rendered content so long values, custom fonts, and branding cannot wrap over the logo or clip by @28allday and @dhh
  • Dismiss open bar panels when clicking on another monitor by @husamemadH
  • Fix background cycling for filenames with glob characters
  • Quantize GTK text scaling to whole point sizes to stop menu clipping
  • Retry failed weather fetches so the bar icon can't stay stuck
  • Remap bar and background surfaces when their monitor moves
  • Persist an integer GDK_SCALE when the monitor scale is fractional
  • Release the sleep inhibitor after locking, and fix a sleep-lock session environment race
  • Rank whole-word app matches above exact menu entries, and route menu ids ahead of app keyword aliases in search
  • Stop the menu from opening on the previous evaluation's stale answers by batching all its guard checks into a single process — 1.5s of checks now run in 0.25s
  • Apply the terminal tag to Omarchy's own terminal and TUI windows, so theme styling and the universal SUPER + C/V clipboard shortcuts work there too
  • Fix suspend and fan defaults on T2 Macs
  • Stop the Tuxedo/Slimbook backlight fix from aborting hardware setup
  • Fix the keyboard-layout migration aborting when vconsole.conf is missing or defines no layout by @acidkill
  • Reopen the Wi-Fi passphrase prompt after a saved password fails, without letting background connection failures steal focus, by @DataDave-Dev
  • Prevent Wi-Fi and Bluetooth device churn from crashing the shell, and keep the network panel from stalling when it opens
  • Open panel hotkeys on the focused monitor
  • Fix hardware-detection false negatives under pipefail, rerun the T2 defaults migration they skipped, and choose the gmux display backlight instead of the Touch Bar on T2 Macs
  • Let browsers set themselves as the default by limiting the BROWSER override to interactive shells
  • Prevent refreshed mise wrappers from overwriting the targets of existing symlinks by @JustMrMendez
  • Fix the WhatsApp Slim migration to target stable Brave Origin and safely handle flags files without a trailing newline by @akashgagda
  • Make mise-managed user tools available to non-interactive SSH commands
  • Keep actions on restored notifications clickable after a shell restart
  • Recover a locked session by restarting the shell when the lock client has died, and persist the shell log across sessions for diagnosis
  • Retake a session lock stranded by a dead shell, so a crash while locked no longer leaves the compositor locked with nothing to authenticate against — the way out used to be a TTY or another machine. The launcher also supervises the shell now, since Qt exits without a signal when the Wayland connection dies and Quickshell's own crash handler never runs by @dhh
  • Find UKIs through a restricted /boot on encrypted installs, fixing direct-boot setup and stale-UKI cleanup
  • Keep concurrent model-usage collectors from colliding, and exclude non-Anthropic providers that merely use the Anthropic messages protocol from Claude totals by @dalmasluca
  • Restore visible Helix cursorlines, rulers, selections, and picker highlights after the semantic theme-color migration by @lukehsiao
  • Keep single-mark coding-agent providers such as Claude from falling back to the generic glyph on light themes
  • Place newly installed Tailscale widgets with the right-side status items
  • Stop huge clipboard entries and file selections from freezing the picker or desktop, while still pasting their full contents, by @markbus-ai
  • Close three code-execution paths a malicious theme could take through install (colors.toml values reaching GNU sed's e flag, an unescaped VS Code theme name, unvalidated keyboard RGB), and drop the unconstrained tzupdate sudoers grant that let any wheel user write a root-owned symlink anywhere by @KazeTachinuu
  • Fix a pinned monitor rule that referenced omarchy_monitor_scale having the variable name itself read as the scale, dropping clamshell recovery to a hardcoded 2x by @s-gato
  • Recover a monitor Hyprland brought up at 0x0 because it was powered off at boot: the connector never drops at DRM level, so powering the screen on fires no event and it stayed black until a reboot by @dhh
  • Apply the Broadcom Wi-Fi quirk to Macs without a T2, gating on the PCI IDs brcmfmac actually binds rather than the T2 bridge, and repair machines installed before it shipped — these fail the WPA four-way handshake against a WPA2/WPA3 transition-mode access point and report the password as wrong by @cupatea
  • Detect Elan match-on-chip fingerprint readers again, and configure the PAM stacks only once a finger has actually enrolled and verified rather than on detection alone by @meirdick
  • Fix the keyboard layout label sticking on ENG where the seat's first keyboard is a radio-control device, or where fcitx5's virtual keyboard takes the main flag on every injection by @artfwo and @dhh
  • Hold the hidden-indicator peek open while the pointer is anywhere on the bar: revealing the indicators widened the section, slid a widget under a pointer that never moved, and stuttered the bar until you moved away by @scottjones
  • Let omarchy bar put place a widget on a bar it does not recognize, instead of refusing and taking the whole migration chain down with it by @dhh
  • Reach every window the region picker can highlight, and keep it from unbinding a same-key binding out of your own config on the way out by @dhh
  • Tag foot's org.codeberg.dnkl.foot app-id as a terminal so universal copy stops sending CTRL + C into it by @dhh
  • Give non-login shells the system locale, so bash started by SSH or herdr's remote bridge stops printing \u escapes instead of characters by @dhh

Feature presentations

The theme switcher

Switching themes is now a more visual affair: a filterable carousel of live previews for every installed theme, opened with SUPER + SHIFT + CTRL + SPACE or via Style > Theme. You can see the new bar riding on top too.

theme-switcher

The background switcher

Backgrounds get the same treatment on SUPER + CTRL + SPACE: flip through the current theme's backgrounds (plus any you've added) and see exactly what you're picking.

bg-switcher

The launcher and the menu, together

Walker has been replaced by a native launcher with fuzzy and acronym matching, live icon indexing (freshly-installed apps show up with their icons immediately), and the ability to hide entries you never use. Still on SUPER + SPACE.

launcher

And the Omarchy menu is now the same surface. Once the menu could search its own nested entries, there was no reason to keep two palettes with two shortcuts around — so SUPER + SPACE now finds your apps and every Omarchy command from one box. It's a native, filterable command palette living inside the shell: instant response, and extensible through a JSONC file if you want to add your own entries. (Prefer apps only? SUPER + ALT + SPACE opens the menu scoped to just them.)

menu

Control panels

The bar widgets now open real control panels. Audio (SUPER + CTRL + A) has output/input device switching and volume control:

audio-panel

Network (SUPER + CTRL + W) shows live throughput, ping, packet loss, a one-click speed test, DNS provider selection, Wi-Fi QR sharing, and nearby networks:

network-panel

And weather now has a proper forecast panel, which can be pinned to a chosen location instead of following your IP around:

weather-panel

A bar you can grab

There's no settings panel for the bar — you just grab it. Click and hold any empty stretch of bar and drag it toward a screen edge, and a ghost slab previews where it will dock. Drop it on the left or right and it turns into a vertical bar. Double-click empty bar space to toggle transparency. Widgets are added, removed, and rearranged with omarchy bar plugin, and third-party widgets install straight from git.

bar-left

Privilege escalation with context

Omarchy now uses pkexec/polkit for privileged operations, with a themed prompt that tells you exactly what command you're authorizing before you type your password.

polkit

Omawrite

Typora is out, and Omawrite is in. A dead-simple Markdown writing app that opens in a blink, autosaves as you type, and stays out of the way. Bound to SUPER + SHIFT + W. Free software.

omawrite

Omacut

A dead-simple video trimmer to go with it. Drop in a clip — like the screen recordings Omarchy takes — drag the handles on the filmstrip, and export the cut with ffmpeg doing the heavy lifting.

omacut

This PR description is up to date as of 27d1b6be; next time, review commits after this one.

@dhh
dhh changed the base branch from dev to master July 17, 2026 18:22
Comment thread shell/plugins/panels/tailscale/Model.js Outdated
function isMullvadPeer(peer) {
var hostName = String((peer && peer.HostName) || "").toLowerCase()
var dnsName = cleanDnsName((peer && peer.DNSName) || "").toLowerCase()
return dnsName.indexOf(".mullvad.ts.net") !== -1 || hostName.indexOf(".mullvad.ts.net") !== -1
Comment thread shell/plugins/panels/tailscale/Model.js Outdated
function isMullvadPeer(peer) {
var hostName = String((peer && peer.HostName) || "").toLowerCase()
var dnsName = cleanDnsName((peer && peer.DNSName) || "").toLowerCase()
return dnsName.indexOf(".mullvad.ts.net") !== -1 || hostName.indexOf(".mullvad.ts.net") !== -1
Comment thread shell/plugins/panels/tailscale/Model.js Outdated
var country = sliceTableColumn(line, countryStart, cityStart)
var city = sliceTableColumn(line, cityStart, statusStart)
var status = sliceTableColumn(line, statusStart, -1)
if (host.indexOf(".mullvad.ts.net") === -1) continue
@jheuing

jheuing commented Jul 20, 2026

Copy link
Copy Markdown

Great work!!!

SUPER + ALT + [ / ]

Can we have a different key to / as in german / swiss keyboards, this is available via shift. So it would be SUPER+ALT+SHIFT+7. Usually having / and \ are not working keyboard shortcuts and need manual fixing.

@jheuing

jheuing commented Jul 20, 2026

Copy link
Copy Markdown

BTW: I can suggest alternatives and provide an MR if we're open to this change incl. past shortcuts...

@emo333

emo333 commented Jul 20, 2026

Copy link
Copy Markdown

I just YOLO'd my 3.8... Workin like a boss. Nothing but what was expecting and a few pleasant surprises. Much appreciation to all of ya'll who made this happen!

@jzetterman

Copy link
Copy Markdown
Contributor

Running Quattro as my daily driver (upgraded an existing install, large 4K display (LG 48GQ900-B 48” OLED) at scale 1). The unified text-size knob is a great idea, but it hard-links three surfaces that don't always want to move together, and there's no way to express "just the shell" for example.

Concrete case: I wanted the shell components (bar, menus, notifications) larger. Moving the display-panel slider did that, but it also:

  1. Set GTK text-scaling-factor, which inflated every Chromium/Electron app (browser, Discord, Teams). I didn't want those touched.
  2. Silently rewrote my hand-tuned font-size = 16 in ~/.config/ghostty/config down to 11pt (I'd picked the 14px stop). No warning, no backup.

There's also a small legibility trap in the mapping: because of the 12px→9pt anchor, the number you pick is never the number that lands in your terminal — e.g. the slider stop labeled "16" writes 12pt into terminal configs.

The three legs are already independently settable underneath ([font] base-size in ~/.config/omarchy/shell.toml, gsettings, terminal configs), so I got what I wanted manually. But that means the knob is unusable for anyone tuning per-surface, one touch of the slider re-links everything.

Proposal: scope flags on omarchy-display-text-size, something like --shell, --gtk, --terminals, defaulting to all three so the one-knob behavior is unchanged. The panel slider keeps calling it with no flags. Happy to submit this as a follow-up PR if there's interest.

lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 22, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy: the
git checkout at ~/.local/share/omarchy becomes a pacman-owned symlink,
Hyprland config converts from .conf to Lua, Waybar is replaced by a
Quickshell shell configured through shell.json, and generated theme
state moves from ~/.config/omarchy/current to
~/.local/state/omarchy/current. This patch rewrites the overlays
quattro-native. The theme-set and update-perform script overrides
shrink to two hooks (zellij retint on theme-set, rustup/cargo on
post-update) because user themed templates, helix restarts, cwd-aware
terminal launches, mise updates, and persistent workspaces 1-5 are now
stock. The hypr .conf files become Lua unbind/rebind overrides, waybar
becomes a shell.json carrying only the clock format and Berkeley Mono,
and the theme symlinks plus alacritty/ghostty imports re-point at the
new state dir. My themed/*.tpl files survive untouched since upstream
kept legacy colorN aliases.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run omarchy-upgrade-to-quattro, reboot, merge this bookmark, then
chezmoi apply (accepting the .chezmoiremove deletions), re-run
omarchy-theme-set, and reload Hyprland. Quattro is still alpha, so
re-verify the hook names and shell.json schema against upstream first.

Tested: chezmoi execute-template renders input/monitors for every
dpi/mousedpi branch and luac -p passes on all of them plus bindings.lua
and looknfeel.lua; shell.json validates; chezmoi status shows the
expected adds/deletes. NOT TESTED on a real Quattro install. The
monitor-relative floating window size in looknfeel.lua needs checking
against hl.window_rule serialization on a live system.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-fable-5
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 23, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy: the git
checkout at ~/.local/share/omarchy becomes a pacman-owned symlink, Hyprland
config converts from .conf to Lua, Waybar is replaced by a Quickshell shell,
and generated theme state moves from ~/.config/omarchy/current to
~/.local/state/omarchy/current. This patch rewrites the overlays
quattro-native. The theme-set and update-perform script overrides shrink to
two hooks (zellij retint on theme-set, rustup/cargo on post-update) because
user themed templates, helix restarts, cwd-aware terminal launches, mise
updates, and persistent workspaces 1-5 are now stock. The hypr .conf files
become Lua unbind/rebind overrides, and the theme symlinks plus
alacritty/ghostty imports re-point at the new state dir. My themed/*.tpl files
survive untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are gone
(upstream's stock layout and ISO-week clock are fine), and its fontFamily key
never fed the bar anyway: the bar binds font.family to the "monospace" alias,
so Berkeley Mono now lives in ~/.config/fontconfig/fonts.conf, the file
`omarchy font set` owns and the shell, Qt apps, and everything resolving
"monospace" read. Berkeley Mono has no Nerd glyphs, so fonts.conf adds a Nerd
Font to the monospace fallback to keep bar and TUI icons rendering; upstream
needs no such entry because its monospace alias is itself a Nerd Font.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it deletes
the .conf and waybar files the live session reads. Upgrade day: run the
quattro upgrade, reboot, merge this bookmark, then chezmoi apply (accepting the
.chezmoiremove deletions), re-run omarchy-theme-set, and reload Hyprland.
Quattro is still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. xmllint validates fonts.conf,
and fc-match against the rendered fonts.conf resolves monospace to Berkeley
Mono Variable and falls back to an installed Nerd Font (JetBrainsMono/Caskaydia)
for glyphs Berkeley lacks. chezmoi status shows the expected fontconfig modify
plus the hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The
monitor-relative floating window size in looknfeel.lua still needs checking
against hl.window_rule serialization on a live system.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-4.8
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 23, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy:
the git checkout at ~/.local/share/omarchy becomes a pacman-owned
symlink, Hyprland config converts from .conf to Lua, Waybar is
replaced by a Quickshell shell, and generated theme state moves from
~/.config/omarchy/current to ~/.local/state/omarchy/current. This patch
rewrites the overlays quattro-native. The theme-set and update-perform
script overrides shrink to two hooks (zellij retint on theme-set,
rustup/cargo on post-update) because user themed templates, helix
restarts, cwd-aware terminal launches, mise updates, and persistent
workspaces 1-5 are now stock. The hypr .conf files become Lua
unbind/rebind overrides, and the theme symlinks plus alacritty/ghostty
imports re-point at the new state dir. My themed/*.tpl files survive
untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are
gone (upstream's stock layout and ISO-week clock are fine), and its
fontFamily key never fed the bar anyway: the bar binds font.family to
the "monospace" fontconfig alias. Berkeley Mono is installed out of
band (it's paid, so unlike my other fonts it isn't committed here),
and the alias is set with `omarchy font set "Berkeley Mono Variable"`,
which rewrites ~/.config/fontconfig/fonts.conf from the current package
default. I don't manage that file in chezmoi for the same reason I
dropped shell.json: omarchy owns and rewrites it, so a committed copy
would both fork an upstream file that still gains fixes (the recent
Arabic Naskh fallback) and fight the next `omarchy font set`.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run the quattro upgrade, reboot, merge this bookmark, then chezmoi apply
(accepting the .chezmoiremove deletions), re-run omarchy-theme-set, run
`omarchy font set "Berkeley Mono Variable"` to point the monospace alias
(and thus the bar) at Berkeley Mono, and reload Hyprland. Quattro is
still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. Confirmed against
upstream Style.qml that the bar's font.family binds to the "monospace"
alias (so shell.json's fontFamily was dead) and that `omarchy font
set` is what writes fonts.conf. chezmoi status shows only the expected
hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The
monitor-relative floating window size in looknfeel.lua still needs
checking against hl.window_rule serialization on a live system.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-4.8
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 25, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy:
the git checkout at ~/.local/share/omarchy becomes a pacman-owned
symlink, Hyprland config converts from .conf to Lua, Waybar is
replaced by a Quickshell shell, and generated theme state moves from
~/.config/omarchy/current to ~/.local/state/omarchy/current. This patch
rewrites the overlays quattro-native. The theme-set and update-perform
script overrides shrink to two hooks (zellij retint on theme-set,
rustup/cargo on post-update) because user themed templates, helix
restarts, cwd-aware terminal launches, mise updates, and persistent
workspaces 1-5 are now stock. The hypr .conf files become Lua
unbind/rebind overrides, and the theme symlinks plus alacritty/ghostty
imports re-point at the new state dir. My themed/*.tpl files survive
untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are
gone (upstream's stock layout and ISO-week clock are fine), and its
fontFamily key never fed the bar anyway: the bar binds font.family to
the "monospace" fontconfig alias. Berkeley Mono is installed out of
band (it's paid, so unlike my other fonts it isn't committed here),
and the alias is set with `omarchy font set "Berkeley Mono Variable"`,
which rewrites ~/.config/fontconfig/fonts.conf from the current package
default. I don't manage that file in chezmoi for the same reason I
dropped shell.json: omarchy owns and rewrites it, so a committed copy
would both fork an upstream file that still gains fixes (the recent
Arabic Naskh fallback) and fight the next `omarchy font set`.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run the quattro upgrade, reboot, merge this bookmark, then chezmoi apply
(accepting the .chezmoiremove deletions), re-run omarchy-theme-set, run
`omarchy font set "Berkeley Mono Variable"` to point the monospace alias
(and thus the bar) at Berkeley Mono, and reload Hyprland. Quattro is
still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. Confirmed against
upstream Style.qml that the bar's font.family binds to the "monospace"
alias (so shell.json's fontFamily was dead) and that `omarchy font
set` is what writes fonts.conf. chezmoi status shows only the expected
hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The
monitor-relative floating window size in looknfeel.lua still needs
checking against hl.window_rule serialization on a live system.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-4.8
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 28, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy:
the git checkout at ~/.local/share/omarchy becomes a pacman-owned
symlink, Hyprland config converts from .conf to Lua, Waybar is
replaced by a Quickshell shell, and generated theme state moves from
~/.config/omarchy/current to ~/.local/state/omarchy/current. This patch
rewrites the overlays quattro-native. The theme-set and update-perform
script overrides shrink to two hooks (zellij retint on theme-set,
rustup/cargo on post-update) because user themed templates, helix
restarts, cwd-aware terminal launches, mise updates, and persistent
workspaces 1-5 are now stock. The hypr .conf files become Lua
unbind/rebind overrides, and the theme symlinks plus alacritty/ghostty
imports re-point at the new state dir. My themed/*.tpl files survive
untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are
gone (upstream's stock layout and ISO-week clock are fine), and its
fontFamily key never fed the bar anyway: the bar binds font.family to
the "monospace" fontconfig alias. Berkeley Mono is installed out of
band (it's paid, so unlike my other fonts it isn't committed here),
and the alias is set with `omarchy font set "Berkeley Mono Variable"`,
which rewrites ~/.config/fontconfig/fonts.conf from the current package
default. I don't manage that file in chezmoi for the same reason I
dropped shell.json: omarchy owns and rewrites it, so a committed copy
would both fork an upstream file that still gains fixes (the recent
Arabic Naskh fallback) and fight the next `omarchy font set`.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run the quattro upgrade, reboot, merge this bookmark, then chezmoi apply
(accepting the .chezmoiremove deletions), re-run omarchy-theme-set, run
`omarchy font set "Berkeley Mono Variable"` to point the monospace alias
(and thus the bar) at Berkeley Mono, and reload Hyprland. Quattro is
still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. Confirmed against
upstream Style.qml that the bar's font.family binds to the "monospace"
alias (so shell.json's fontFamily was dead) and that `omarchy font
set` is what writes fonts.conf. chezmoi status shows only the expected
hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The
monitor-relative floating window size in looknfeel.lua still needs
checking against hl.window_rule serialization on a live system. Re-checked
the overlay against upstream quattro at f4e8470c3a1b: the theme-set and
post-update hook dirs, the themed template renderer's legacy colorN
aliases, the `o.bind` dispatcher tables, `o.window`, and the
floating-window tag all still behave as this patch assumes, and the
state dir the symlinks point at is unchanged. The uwsm/default override
survives too: quattro moved the file into the package, but
/usr/share/uwsm/env.d/10-omarchy still sources ~/.config/uwsm/default,
and the upgrade keeps hash-mismatched user copies active, so EDITOR
stays helix. Worth knowing for upgrade day: the upgrade's always-copy
list rewrites all four hypr .lua files with quattro skeletons, which is
why chezmoi apply has to run after the upgrade rather than before.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-4.8
Assisted-by: Claude:claude-opus-5
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 28, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy:
the git checkout at ~/.local/share/omarchy becomes a pacman-owned
symlink, Hyprland config converts from .conf to Lua, Waybar is
replaced by a Quickshell shell, and generated theme state moves from
~/.config/omarchy/current to ~/.local/state/omarchy/current. This patch
rewrites the overlays quattro-native. The theme-set and update-perform
script overrides shrink to two hooks (zellij retint on theme-set,
rustup/cargo on post-update) because user themed templates, helix
restarts, cwd-aware terminal launches, mise updates, and persistent
workspaces 1-5 are now stock. The hypr .conf files become Lua
unbind/rebind overrides, and the theme symlinks plus alacritty/ghostty
imports re-point at the new state dir. My themed/*.tpl files survive
untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are
gone (upstream's stock layout and ISO-week clock are fine), and its
fontFamily key never fed the bar anyway: the bar binds font.family to
the "monospace" fontconfig alias. Berkeley Mono is installed out of
band (it's paid, so unlike my other fonts it isn't committed here),
and the alias is set with `omarchy font set "Berkeley Mono Variable"`,
which rewrites ~/.config/fontconfig/fonts.conf from the current package
default. I don't manage that file in chezmoi for the same reason I
dropped shell.json: omarchy owns and rewrites it, so a committed copy
would both fork an upstream file that still gains fixes (the recent
Arabic Naskh fallback) and fight the next `omarchy font set`.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run the quattro upgrade, reboot, merge this bookmark, then chezmoi apply
(accepting the .chezmoiremove deletions), re-run omarchy-theme-set, run
`omarchy font set "Berkeley Mono Variable"` to point the monospace alias
(and thus the bar) at Berkeley Mono, and reload Hyprland. Quattro is
still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. Confirmed against
upstream Style.qml that the bar's font.family binds to the "monospace"
alias (so shell.json's fontFamily was dead) and that `omarchy font
set` is what writes fonts.conf. chezmoi status shows only the expected
hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The monitor-relative
floating size in looknfeel.lua is now verified on Hyprland 0.56.0: a
probe window took 1654x1016 on a 3008x1692 logical monitor, so
monitor_w/monitor_h resolve against logical and not physical pixels,
which is what makes 55%/60% right on a retina display. The percentage
form (size 55% 60%) is not a substitute: hyprctl accepts it and then
silently ignores it, leaving the app's own default. What remains
unproven is only the Lua table to rule-string serialization, and
upstream ships that same shape in webcam-overlay.lua. Re-checked
the overlay against upstream quattro at f4e8470c3a1b: the theme-set and
post-update hook dirs, the themed template renderer's legacy colorN
aliases, the `o.bind` dispatcher tables, `o.window`, and the
floating-window tag all still behave as this patch assumes, and the
state dir the symlinks point at is unchanged. The uwsm/default override
survives too: quattro moved the file into the package, but
/usr/share/uwsm/env.d/10-omarchy still sources ~/.config/uwsm/default,
and the upgrade keeps hash-mismatched user copies active, so EDITOR
stays helix. Worth knowing for upgrade day: the upgrade's always-copy
list rewrites all four hypr .lua files with quattro skeletons, which is
why chezmoi apply has to run after the upgrade rather than before.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-4.8
Assisted-by: Claude:claude-opus-5
lukehsiao added a commit to lukehsiao/dotfiles that referenced this pull request Jul 28, 2026
Quattro (Omarchy 4) breaks every hook this repo has into Omarchy:
the git checkout at ~/.local/share/omarchy becomes a pacman-owned
symlink, Hyprland config converts from .conf to Lua, Waybar is
replaced by a Quickshell shell, and generated theme state moves from
~/.config/omarchy/current to ~/.local/state/omarchy/current. This patch
rewrites the overlays quattro-native. The theme-set and update-perform
script overrides shrink to two hooks (zellij retint on theme-set,
rustup/cargo on post-update) because user themed templates, helix
restarts, cwd-aware terminal launches, mise updates, and persistent
workspaces 1-5 are now stock. The hypr .conf files become Lua
unbind/rebind overrides, and the theme symlinks plus alacritty/ghostty
imports re-point at the new state dir. My themed/*.tpl files survive
untouched since upstream kept legacy colorN aliases.

The Quickshell bar carries no user config. shell.json's clock keys are
gone (upstream's stock layout and ISO-week clock are fine), and its
fontFamily key never fed the bar anyway: the bar binds font.family to
the "monospace" fontconfig alias. Berkeley Mono is installed out of
band (it's paid, so unlike my other fonts it isn't committed here),
and the alias is set with `omarchy font set "Berkeley Mono Variable"`,
which rewrites ~/.config/fontconfig/fonts.conf from the current package
default. I don't manage that file in chezmoi for the same reason I
dropped shell.json: omarchy owns and rewrites it, so a committed copy
would both fork an upstream file that still gains fixes (the recent
Arabic Naskh fallback) and fight the next `omarchy font set`.

Do NOT merge or apply this on a machine still running Omarchy 3.x; it
deletes the .conf and waybar files the live session reads. Upgrade day:
run the quattro upgrade, reboot, merge this bookmark, then chezmoi apply
(accepting the .chezmoiremove deletions), re-run omarchy-theme-set, run
`omarchy font set "Berkeley Mono Variable"` to point the monospace alias
(and thus the bar) at Berkeley Mono, and reload Hyprland. Quattro is
still alpha, so re-verify the hook names against upstream first.

Tested: chezmoi execute-template plus luac -p pass on bindings.lua,
looknfeel.lua, and input.lua/monitors.lua rendered across every mousedpi
(high/mid/low) and dpi (retina/mid/low) branch. Confirmed against
upstream Style.qml that the bar's font.family binds to the "monospace"
alias (so shell.json's fontFamily was dead) and that `omarchy font
set` is what writes fonts.conf. chezmoi status shows only the expected
hypr .conf->.lua swaps. NOT TESTED on a real Quattro install. The
monitor-relative floating size in looknfeel.lua is now verified on
Hyprland 0.56.0: a probe window took 1654x1016 on a 3008x1692 logical
monitor, so monitor_w/monitor_h resolve against logical and not physical
pixels, which is what makes 55%/60% right on a retina display. The
percentage form (size 55% 60%) is not a substitute: hyprctl accepts
it and then silently ignores it, leaving the app's own default. What
remains unproven is only the Lua table to rule-string serialization,
and upstream ships that same shape in webcam-overlay.lua. Re-checked
the overlay against upstream quattro at f4e8470c3a1b: the theme-set
and post-update hook dirs, the themed template renderer's legacy
colorN aliases, the `o.bind` dispatcher tables, `o.window`, and
the floating-window tag all still behave as this patch assumes, and
the state dir the symlinks point at is unchanged. The uwsm/default
override survives too: quattro moved the file into the package, but
/usr/share/uwsm/env.d/10-omarchy still sources ~/.config/uwsm/default,
and the upgrade keeps hash-mismatched user copies active, so EDITOR
stays helix. Worth knowing for upgrade day: the upgrade's always-copy
list rewrites all four hypr .lua files with quattro skeletons, which is
why chezmoi apply has to run after the upgrade rather than before.

Ref: basecamp/omarchy#6231
Assisted-by: Claude:claude-opus-5
dhh and others added 15 commits July 28, 2026 07:28
Every install row that leaves a durable artifact now carries a `when`
that mirrors its remove counterpart, negated. Packages check
`omarchy-pkg-present`, Ollama checks the command since the action picks
between three packages, and the rest check whatever the installer
actually leaves behind: Games/battlenet, the Xbox Cloud and Windows VM
desktop files, the flatpak, the mise install dirs, ~/.rustup, ~/.opam,
the composer laravel binary, the phx_new archive.

Chromium Account now shows only when chromium-flags.conf exists and
lacks the oauth client id. It was a visible no-op both before Chromium
was installed and after the flags had been added.

The pickers stay unguarded: Package, AUR, Web App, TUI, Style, Docker
DB, and the RetroArch Game Launcher, which builds one launcher per game.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stop stranding the Launching… OSD when a second app is launched
* Add Tailscale header on/off toggle

* Address Tailscale toggle review feedback

* Hide Tailscale tooltip while busy

* Keep Tailscale labels clear of header toggle

* Extract the switch from Toggle into a reusable ToggleSwitch

Toggle rendered its own track and knob inline, so anything else wanting a
switch had to copy the geometry. Pull it into Ui/ToggleSwitch.qml and let
Toggle compose it, and give PanelHero a trailingControl slot so a hero can
pin a control to its trailing edge without the caller doing the layout.

The switch draws its cursor as a ring outside the track: themes give normal
chrome a stronger border than hover-cursor, which is right for controls that
are borderless at rest but would make a bordered track go fainter under the
cursor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the panel cursor on the virtual header section

clampCursor resets focusSection whenever it is not in visibleSections, but
"header" is virtual and never appears there. Any refresh of the underlying
model therefore threw the cursor off the hero toggle: muting republishes the
PipeWire snapshot, and toggling the Bluetooth adapter empties and refills the
device lists. moveCursor already special-cases "header"; clampCursor now does
too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Toggle panel heroes with a switch instead of the status icon

The hero icon doubled as the on/off control, which was invisible as an
affordance and made the icon carry two jobs at once. Give Tailscale, Dropbox,
Bluetooth, Audio, and Network a ToggleSwitch on the trailing edge of the hero
and leave the icon to report status. The switch is the header's only cursor
target, so the keyboard reaches it the same way the mouse does.

Dropping the icon's focus ring also drops heroRingPad, which lets each hero
line up with the rows beneath it. Network's link detail moves inline after the
name -- "Ethernet (2.5gbit)" -- since the pill crowded the switch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the network hero switch honestly a Wi-Fi switch

The switch reads and writes Networking.wifiEnabled, but its tooltip claimed to
turn "network" on and off whenever Ethernet was the active connection. A click
asserted nothing; a switch asserts state, so on a wired machine with the radio
off it sat there reading "off" beside a perfectly live Ethernet connection.

Say Wi-Fi, and only offer the switch when there is a radio to switch.
headerActionCount follows the same condition so the keyboard cannot reach a
control that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Stop the header cursor claiming an absent switch

The switch hides when the thing it toggles is unavailable -- no Tailscale CLI,
no Dropbox CLI, no Wi-Fi radio -- but "header" stayed reachable, so the cursor
could sit on a target that never rendered. The old clickable icon was always on
screen, so there was always something to highlight.

"header" stays navigable and Enter still no-ops safely; the cursor just stops
claiming a spot that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Wrap the gallery's switch caption

The caption sat unbounded inside the switch row. The gallery has a 560px
minimum width, horizontal scrolling off, and clipping on, so at that size the
end of the line was simply unreachable. Move it below the row and wrap it, the
way every other description in the gallery already does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sliders only ever accepted the left button. Audio wants right-click as a
secondary action on the track, so emit rightClicked() for it and keep dragging
left-button-only: press and release both ignore anything else, so a right-click
can neither start a drag nor move the value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hero switch is the panel's on/off, so it now carries output and input
together rather than output alone. It reads as on while anything is still
audible, which keeps muting a single channel from flipping the master switch.
Right-clicking a volume bar mutes just that channel -- output, input, or one
app's stream -- matching what the `m` key already does for the focused row.

Only channels that exist get a vote: a machine with no default source would
otherwise report its input unmuted forever, leaving the switch able to mute but
never to unmute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clipboard history is fed by two wl-paste --watch processes started once
when the shell loads. When one dies, nothing notices: copying still
works, the picker still opens, and the old entries are all still there,
so history just silently stops recording until the next shell reload.

Respawn them on exit instead. The one-shot timer both coalesces the case
where both watchers die together and keeps a wl-paste that cannot start
at all from spinning as fast as fork allows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The shell starts before DHCP finishes, so Claude's first probe of the
OAuth usage endpoint came back as a transport failure and pinned "Claude
limits unavailable" for a full refresh interval. Opening the panel could
not clear it either: the open path refreshed unforced and hit the same
throttle it was trying to escape.

A transport failure now arms a short retry that goes straight to the
probe, since a retry answering to the throttle never gets off the
ground. Any real answer disarms it — a server that replied, 429
included, is one to stop pestering. Probes are single-flight, a fresh
token skips the throttle its predecessor set, and opening the panel asks
for the wire without re-walking every transcript on disk.

The module also earns its place in the bar now instead of sitting there
dimmed with nothing to say: it collapses out entirely until a provider
has actually recorded usage, here or on a synced machine. That made both
presence probes redundant, so they are gone. Selection follows the
provider rather than its slot, so one appearing while the panel is open
no longer swaps out what you were reading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The edge channel installs omarchy-dev, but omarchy-version only queried
omarchy, so it exited 1 there. omarchy-snapshot runs under set -e, so the
failed lookup aborted the whole update over a snapshot label.

It only worked at all because omarchy-dev declares provides=(omarchy) from
the installer repo. Builds without it fail, so check both packages here
instead of relying on a declaration from another tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…der-corners

Fix asymmetric rounded shell borders
dhh and others added 7 commits August 11, 2026 05:32
Opening the cheatsheet outright put a menu in front of someone who had not
asked for one, and it blocked first run until they dismissed it. Go back to a
toast that opens the same menu when clicked.

The body carries real newlines now. It was written with a literal \n, which the
card renders as the two characters rather than a line break.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Previously, right-clicking the audio icon muted only the output. Whereas
the switch at the top of the panel mutes all audio (inputs and outputs).

Pairing the right-click to the switch seems a bit less confusing, and
also matches the behaviour with other panels (e.g. Bluetooth, Tailscale,
etc).
* Fix unclickable tray submenus by drilling down inside the popup

Clicking a tray menu entry that has children was a silent no-op: the
row called QsMenuEntry.display(), which renders a *platform* menu, and
Quickshell refuses that unless the shell root sets `//@ pragma
UseQApplication` -- shell.qml does not. The log shows "Cannot display
PlatformMenuEntry as quickshell was not started in QApplication mode"
and nothing opens. Apps whose whole menu is submenus, like
radiotray-ng's station list, were unusable.

Adding the pragma would be the wrong fix: it switches the entire shell
from QGuiApplication to QApplication, dragging QtWidgets into the
process and changing application-class behavior for the sake of one
popup -- which would then render as an unstyled platform menu beside
omarchy's own popup styling anyway.

Instead, submenus drill down inside the existing popup. A child
QsMenuEntry inherits QsMenuHandle, so it can feed a nested QsMenuOpener
and render through the same row delegate. Each level keeps its own live
opener on a stack -- a child entry is owned by its parent opener's
model, so collapsing to a single reassigned opener would destroy the
very entry being displayed. A back header row walks out one level; at
the root the menu renders exactly as before, and items without a
DBusMenu still use the platform fallback.

* Destroy submenu openers deepest-first and reset before switching items

resetTrayMenu() destroyed openers front-to-back and only cleared
submenuStack afterward. A deeper opener's menu entry is owned by its
parent's children model, so destroying the parent first could
invalidate an entry a still-live child opener referenced. Clear the
stack before tearing anything down, then destroy deepest-first so a
child is always gone before the parent whose model owns its entry.

openTrayMenu() reassigned activeTrayItem before calling resetTrayMenu().
trayMenuOpener.menu binds to activeTrayItem.menu, so that reassignment
immediately swaps what the root opener's children expose -- invalidating
entries any live submenu opener still referenced, before resetTrayMenu()
got a chance to tear them down. Reset first, then switch items.

Thanks @Copilot for catching both.

* Defer submenu reset until the popup's fade-out actually finishes

onTrayMenuOpenChanged reset the submenu stack the instant trayMenuOpen
went false, but the popup stays visible for the whole 140ms opacity
fade (PopupCard's own visible: open || card.opacity > 0) -- dismissing
from a submenu flashed the root menu mid-fade, and could resize or
reposition the fading popup if the two have different geometry.

Moved the reset to trayMenuPopup's own onVisibleChanged, which only
fires once the fade has genuinely completed. Switching to a different
tray item is unaffected: openTrayMenu() already resets explicitly
before assigning the new item, independent of whether the popup ever
dips to invisible (rapid reopen mid-fade never does).

Thanks @Copilot for catching this.

* Ignore tray menu clicks for a beat after changing submenu level

Changing level swaps the Repeater's model, which rebuilds the row
delegates synchronously -- a fresh row lands under a cursor that hasn't
moved. Submenu clicks used to be silent no-ops, which trained users to
click them twice, so that second click now fires whatever entry took
the spot. On radiotray-ng that means an accidental station switch.

Gate row and back-header clicks for 250ms after each level change. A
deliberate follow-up click is slower than that; a double-click is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Pin the submenu back header above the scrolling menu rows

The back header lived inside the Flickable's Column, so in a submenu
taller than the 420px cap -- exactly the long station list this
drill-down exists for -- scrolling down pushed the only way back off
screen, with no Escape or right-click alternative.

Move it into a pinned Column above the Flickable and account for its
height in the popup's contentHeight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Reset the tray menu scroll offset when the drill-down is torn down

Flickable keeps its contentY across a model swap whenever the new
content is still tall enough to hold it. A menu dismissed while
scrolled therefore reopened part-way down with its first entries off
screen: reproducible on any tray app whose root menu outgrows the
420px cap, and now reachable on every app once a long submenu has
been scrolled.

Zero the offset in resetTrayMenu(), which runs both on teardown and
before switching items.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Toni Nowak <t.nowak@ai-flow.no>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Coding agents refuse to persist trust for $HOME, so the keybinding and
menu launches -- which inherit the session's ~ cwd -- re-asked on every
single session. ~/Work is already the work root Omarchy creates, and
trust there sticks.

Only applies when the cwd is exactly $HOME, so the inline alias still
starts in whatever project directory it was run from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(monitor): prevent mirror toggle deletion during recovery and fix UI state

* Assert the external monitor helper counts mirrors as active

The helper now asks `hyprctl monitors all -j`, so the test that pinned it
to plain `monitors` failed. A mirrored external is absent from plain
`monitors`, which reads as a disconnect and hands the mirror toggle to
recovery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the mirror state line when nothing is mirrored

Piping the first mirroring monitor into the branch fed jq's test() a null
whenever no output mirrored, and jq aborts there rather than falling back
to "". The panel reads this output by line, so the missing line shifted
the focused monitor, the scale, and the display list up one, and left
mirroring reading as on whenever the external display had focus.

Select first and branch inside the pipeline, so the branch only ever sees
a monitor and the empty case falls to "" as the lines around it do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Cover the monitor panel state the shell reads by line

Nothing exercised omarchy-monitor-state, so both the mirror direction it
reported and the jq that reported it went unguarded. The panel reads the
output by line index, where a helper dying mid-script costs a line and
shifts every field below it into the wrong property without failing.

Assert the line count alongside the fields, over extended, mirrored both
directions, and clamshelled displays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
lspci reads PCI config space, and the kernel resumes a runtime-suspended
device to serve that read. On a hybrid laptop the discrete GPU idles in
D3cold, so the first lspci of a Hyprland config load spends over a second
waking it — longer than the 1.5s budget Hyprland gives the whole load.
The reload then fails at whichever line runs next, which is why the error
pointed at default/hypr/apps/1password.lua rather than at nvidia.lua.

Read the vendor, class, and device IDs from sysfs instead. Those are
served from cached fields and never touch config space, so nothing wakes
up. Classify by device ID while we're here: Turing is both the first
generation with GSP firmware and the first at 0x1e00 or above, and
Maxwell opens at 0x1340, one ID past the last Kepler part. Bounding the
older detector at both ends keeps pre-Maxwell cards off the 580xx driver
that cannot drive them, and picks up the Maxwell and Pascal parts the
lspci name regex used to miss.

omarchy-hw-nvidia was also checked in without its executable bit, which
it needs now that nvidia.lua runs it.

Fixes #6660

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Tsurgcom

Copy link
Copy Markdown
Contributor

Looks cool, LGTM! 🚀

dhh and others added 22 commits August 11, 2026 22:09
Herdr ships its own annotated keybindings menu, same as Tmux, but nothing
in the menu pointed at it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The row list was capped at 60% of screen height so a card could never read
as a page. On a laptop-height display that folds the starting menu one row
early, hiding About behind the peek for no gain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every other pointer at the community lives in an error path or a doc, so
there was no way to reach it from the menu. Prefer the Discord app when
it is installed, and fall back to the invite in a browser when it is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Repair theme symlinks the state-move migration left dangling

1781043107.sh re-linked legacy theme symlinks whose targets were stored
with a literal "~/.config/omarchy/current/..." string. The replacement
used the same literal tilde, which the filesystem never expands inside a
symlink target, so btop, Helix, and VS Code/Cursor lost their theme and
the migration reported success anyway.

Fix the source migration to relink through the already-defined
$current_state_dir variable, and add a follow-up migration that repairs
the links the applied version left dangling — matching the existing
1785002349.sh pattern, so it is idempotent and leaves custom links alone.

Co-Authored-By: Claude <noreply@anthropic.com>

* Only repair theme symlinks that could never have worked

The repair matched any target containing omarchy/current, so a working link
into a user's own dotfiles was rewritten to the state directory and their
setup was lost. Claim a link only when its target starts with a literal "~/",
which the filesystem never expands, and names this exact theme file: that is
what 1781043107.sh wrote, and no working link can look like it. A dangling
target is not enough on its own, since a dotfiles repo may just be unmounted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Clean up every scratch directory the CLI suite creates

Five of the eight mktemp directories were never registered with the exit
trap, so each run left them behind in /tmp. Route them all through a helper
that records them for cleanup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Drop the theme symlink migration tests

A migration runs once on each machine and is then inert, but a test for it
sits in the suite forever. Now that the repair behaves correctly, keep the
migration and let it go untested rather than grow the suite permanently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
…#6716)

* Accept either name for the lock authentication command

omarchy-setup-lock was renamed to omarchy-apply-lock in 536fcd5, but
the upgrade calls into whatever the channel just installed, and every
released package still ships the old name. The rename only moves in
lockstep for the ISO, which installs the runtime from the mirror it
ships with; the upgrade has no such guarantee, so it aborted every run
with "omarchy-apply-lock is unavailable" right before the point of no
return.

Prefer the new name and fall back to the old one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Leave the Omarchy 3 session alone until the reboot

The upgrade tried to start Quickshell in the live session, and only
stopped waybar/walker/elephant if that worked. Every branch of it could
fail, so it needed a warning for each, and those warnings were the first
thing users read at the end of an upgrade that had otherwise succeeded.

The reboot is the cutover. Swapping the UI out underneath a running
session buys nothing, so drop the attempt and both functions with it.
The Omarchy 3 bar, launcher, and notifications keep working until the
reboot, which is what happened anyway whenever the start failed.

Also warn up front when the live Hyprland session cannot be reached, and
drop the package_mode label that was set on both branches of the
dev-package check and never read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Shim the legacy Hyprland defaults from the backup, not from GitHub

populate_legacy_hypr_defaults tried a sparse git clone of master, then a
curl of the master tarball, and only if both failed the backup taken a
few lines earlier. That backup is the checkout the running session is
sourcing right now: it is the correct content, it is already on disk,
and it needs no network in the middle of an upgrade. master is a guess
that is wrong for any machine not on master.

Try the backup first and drop the git clone, which fetched the same
thing as the tarball by a longer route. The network path stays for the
case that has no backup, where the legacy root was already a symlink.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Clear the Hyprland error bar the upgrade leaves behind

The two settings were applied through both the keyword and the Lua API,
in four calls on the way out and three on the way in, split differently
each time. Fold that into one helper that takes the value.

The error bar needed more than suppression. An explicit hyprctl reload
re-reads the config from disk, which resets both keywords before it
reports what it found, so suppression cannot survive one; anything that
reloaded during the swap left the bar on screen. Hyprland then keeps it
up until a later clean reload, which this script deliberately never
performs, so it was still there when the upgrade finished. Clear the
overlay on the way out, once the shims have made the legacy config
resolve again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the packaged firewall config instead of a copy of it

configure_snapper_policy and configure_lock_authentication already call
into the installed tree; apply_firewall_defaults reimplemented
install/config/firewall.sh inline instead, and had already drifted from
it. The packaged script also installs the ufw-docker rules, so upgraded
machines came up without the Docker firewall protections a fresh install
gets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Stop the Voxtype toggle migration reloading a mid-swap session

This is the reload behind the "source= globbing error" bar users see
during a Quattro upgrade. The upgrade runs the packaged migrations
against a still-running Omarchy 3 session, and this one ends with
hyprctl reload. That re-parses a legacy config whose theme source= has
nothing to resolve to yet, so Hyprland paints an error bar and keeps it
up until a later clean reload the upgrade deliberately never performs.
It was still on screen when the upgrade finished.

The upgrade already exports OMARCHY_UPGRADE_TO_QUATTRO_LIVE for exactly
this, and 1782002156 honors it. Do the same here. Nothing in that
session reads the toggle being removed; the reboot applies it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Assert the retired session entry points stay gone

The ordering check would still pass if either came back, while the
comment above it claims they cannot. Name them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Upgrade onto the channel the machine is already on

The upgrade always defaulted to stable, so an rc machine taking it
landed on production packages from the stable repo. rc callers worked
around that by passing --channel rc, which then forced every caller
onto rc, stable machines included.

Read the channel off the mirrorlist the way omarchy-version-channel
does and follow it: stable machines get omarchy and omarchy-settings
from stable, and rc or edge machines get omarchy-dev and
omarchy-settings-dev from edge, which is what --dev already selects.
An explicit --channel or --dev still wins, and an unrecognized
mirrorlist still falls back to stable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep rc machines on the rc mirror when taking dev packages

--dev forced the edge channel, so an rc machine following its own
channel was moved onto the edge Arch mirror as well. The constraint is
narrower than that: the dev packages are only published to the edge
package repo, which the rc and edge channels both already point at.
Only stable is incompatible.

Reject --dev only for stable, and default to edge just when no channel
was chosen. rc machines now upgrade against rc-mirror with the dev
packages out of the edge repo, which is where Quattro lives until it
ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
omarchy-monitor-state stopped shelling out to
omarchy-hyprland-monitor-focused when it started deriving the focused
name from its own hyprctl snapshot, so the stub the test installed was
never called and the assertion could never pass. Expect the focused
monitor from the fixture instead, and drop the dead stub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sleep inhibitor deliberately outlives the start that spawns it, but
script tears its pty down as soon as the command returns, and the SIGHUP
that follows could kill the inhibitor before it managed to exec. The
sudo stub then never logged and the test failed about half the time.
Hold the session open from inside until the inhibitor has started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stub polled for the lock with its own flock, competing with the
holder it had just started. The holder took the lock non-blockingly and
never retried, so a lost race killed it and left the lock free. The
notifier then saw no update in progress and sent the toast the test
asserts it withholds. Wait on the holder's own signal instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1784917531 gated its UKI rebuild on initramfs_async=0 being present in
the Limine config, but omarchy-settings ships omarchy-defaults.conf with
that parameter already in it. Any machine that installed the package and
ran the migration in the same update matched the config the package had
just written, skipped the rebuild, and kept booting an image baked
before the config existed — without initramfs_async=0, so encrypted
boots still fell back to an unthemed text LUKS prompt. Compare the
booted command line against the configured one and rebuild when they
disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every supported agent spells it differently, so map each one to its own
bypass flag instead of leaving the launcher at each agent's default.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
History was a pair of in-memory lists mirrored into notifications.json, split
into "pending" and "past" by a seen/unseen distinction no surface exposed,
capped at 100, deduped by an id that repeats across server generations, and
pruned by a 15-minute TTL. Replaying it showed five rows drawn from whichever
list happened to hold them.

Every toast already writes a file under ~/.local/state/omarchy/notifications
so it can survive a shell restart. That file is now the history record: when
the popup leaves the screen it moves into notifications/history instead of
being deleted, the newest ten are kept, and showHistory replays exactly what
is in there, including the toasts still on screen when it is asked for. A
notification DND silenced is written straight into the same directory, since
a toast that never showed is the one worth looking back at.

That leaves the models, notifications.json history payload, past pruning, and
the /tmp image cache that existed to keep century-old history thumbnails alive
with nothing to do, so they go.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…laced

A client that updates a notification through replaces_id does not produce a
second onNotification: Quickshell writes the new content onto the Notification
object the shell is already holding. The card draws a snapshot copied out of
that object — deliberately, since a live QObject in a ListModel role becomes a
dangling pointer the moment the server destroys it — so the toast kept showing
the superseded text, and archived it to history when it left the screen. A
Slack thread that updates in place read as stuck.

Every property the card draws is now watched on the notification we hold, and
a change rewrites both the model row and the file the popup was persisted
under. The file name is that popup's identity, so the rewrite lands in place:
a shell restart restores the version last shown, and so does the copy that
reaches history.

The countdown starts over when the content changes. New text arriving a second
before the toast was due to expire deserves a full look, not the remainder of
the clock the text it replaced had nearly run through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pacman cache grows without bound across updates, and nothing in the
update flow ever reclaimed it. On a machine that has been updating for a
while it reaches several gigabytes of superseded versions that nothing
will ever install again.

Prune it with paccache -rk2 as the first step of an update. Both halves
of that placement are load-bearing.

Keeping two versions rather than one preserves the rollback path. The
cache is Arch's only offline downgrade: when an update breaks a single
package, reinstalling its predecessor from here is the surgical fix,
where a snapshot rollback would revert every other package too. Pruning
before the packages update means the installed version is still the
newest cached, so it survives along with a spare. Retention is by
version order and never consults what is installed, so that holds while
the installed version is among the two newest cached; a deliberate
downgrade or repeated failed transactions can stack newer archives on
top of it.

Running before the snapshot is what actually frees the space. The cache
sits on the snapshotted root subvolume, so a prune taken afterwards
leaves the fresh snapshot holding those extents and reclaims nothing
until it ages out of the number cleanup.

A failed prune warns and continues. Cache housekeeping should not trip
the update's ERR trap and tell the user their update went wrong.

This runs after omarchy-update-requires-free-space, so it reclaims space
during healthy updates but does not rescue a machine already under the
10 GiB gate.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Replay the history a dismissal or a clear was still being written into

The popup files a replay reads are written by a serialized queue of shell
jobs, and the read ran as its own process alongside it. A dismissal issued a
moment earlier could still be queued when the directory was read, leaving the
notification out of the replay it was the newest entry of, and a clear issued
a moment earlier could still be queued too, replaying entries it was about to
remove.

The read now waits for the queue to go idle, so the replay shows the history
as of the moment it was asked for rather than whichever jobs happened to have
landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Catch up on an update that arrived before its popup had a row

Watching a notification for in-place updates starts the moment it is handed
over, but the row those updates write to is inserted a tick later, deferred to
keep a mid-incubation Repeater from being mutated underneath. A client fast
enough to update inside that window found no row to write to, and a property
that has already changed does not change again — so the toast and its file sat
on the superseded content until something else moved.

The row is now refreshed from the live notification once it exists. That reads
the same object the signals would have, so an update that beat the insert is
picked up and one that did not costs nothing: a refresh whose content matches
the row it would write is dropped, which also collapses the several signals a
single multi-property update emits into one rewrite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Hold queued file work behind the replay's read, not just ahead of it

The read waited for everything queued before it, but nothing stopped the queue
from running on while it worked. A clear or an archive issued during the read
could delete or move files out from under awk mid-glob, so a replay could still
show a partial history — some of what a clear was in the middle of emptying.

The read is a barrier in both directions now: the queue holds until it exits,
and it releases on exit rather than on output, so a read that comes back empty
or fails cannot park the queue behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Queue the replay's read instead of waiting for the queue to empty

Waiting for the queue to go idle before starting the read still let work
overtake it. A clear or an archive enqueued after the replay was asked for,
while the current job was running, was dequeued the moment that job exited —
the read only starts once nothing is left — so the replay showed the state
after those jobs, which is the race this was meant to close. Unbroken file
traffic could postpone the read indefinitely for the same reason.

The read is now an entry in that queue rather than a process running beside
it. It takes its place in line behind the work queued before the request and
ahead of everything queued after, so no later job can overtake it and no
amount of traffic can push it back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The widget polled hyprctl every 10 seconds per monitor, including on the single-layout install where it never shows. Keep the poll only where its answer can change - a seat with more than one keyboard, where Hyprland moves the main flag with no event to announce it - and stop it entirely once a one-keyboard seat has been read.

Also coalesce a refresh that arrives mid-query instead of dropping it, time out a query that never returns rather than letting it hold the guard shut for good, and re-read the layout on configreloaded.

Co-Authored-By: markbus-ai <markbus-ai@users.noreply.github.com>
* Restore FPC fingerprint detection

* Anchor the FPC product match to a prefix

*fpc* is an unanchored three-letter token on the one branch that is
trusted outright, with none of the kernel-driver checking the vendor
guess gets. Every FPC reader on record leads with it — "FPC Sensor
Controller", "FPC Sensor Controller L:0002 FW:25.26.23.14", and this
branch's "FPC L:0000 FW:1425046" — so requiring the prefix costs no
coverage while keeping three letters from matching mid-string, where
FPC abbreviates unrelated things like flexible printed circuit.

Also restore the note about why Elan is kept out of the vendor list, so
the two signatures read as the same deliberate exception.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Write no product descriptor for a two-field device spec

${remainder#*:} returns the string unchanged when there is no second
colon, so a spec meant to describe a device with no product descriptor
wrote the product id out as its product string instead. Every call site
happened to pass a trailing colon, so the suite was right by accident.

Guard the split and drop the trailing colons, so the two vendor-match
cases exercise the path they were written for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bind a named driver in the kernel-driver fixture

Touching a bare `driver` file asserts that any driver at all disqualifies
a vendor guess, which is more than the detector should promise: libusb
claims an interface through a synthetic `usbfs` driver, so a reader in
active use looks bound by that rule.

Link the interface at a named driver directory instead, the way sysfs
does. The case still covers what it was written for — a Synaptics bridge
or a camera on a fingerprint vendor ID — without fixing the shape of the
answer for drivers it was never about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The vendor-ID guess rejects any device with a driver bound, on the
reasoning that libfprint drives readers from userspace so a real one sits
there unbound. But libusb claims interfaces through a synthetic usbfs
driver, so the reader binds one for as long as fprintd holds the claim —
which is exactly while it is being enrolled or verified against.

Readers that name themselves take the product-string branch and never
reach this, so the exposure is the ones that don't: Goodix 27c6:6594
reports "Goodix USB2.0 MISC", matches on vendor ID alone, and drops out
of detection mid-authentication. The menu entry disappears and the
first-run invitation stops firing while the reader is in use.

Ignore a driver link that resolves to usbfs, and keep rejecting the real
ones — usbio-bridge, usbhid, uvcvideo — including on a device that has
both.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drops the ChatGPT web app from the default set at the same time, so installing
the openai-codex-desktop package can't leave two identical-looking ChatGPT
entries in the launcher. Super + Shift + A still opens the web version, which
is the only place it was really used.

The bundled ChatGPT icon stays: the package's own chatgpt.desktop asks for
Icon=chatgpt and ships no hicolor icon of its own.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.