Skip to content

Omarchy Quattro - #6231

Draft
dhh wants to merge 1790 commits into
masterfrom
quattro
Draft

Omarchy Quattro#6231
dhh wants to merge 1790 commits into
masterfrom
quattro

Conversation

@dhh

@dhh dhh commented Jul 17, 2026

Copy link
Copy Markdown
Member

Omarchy Quattro: Release Candidate

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 by first Update > Omarchy, then Update > Channel > RC, then Update > Omarchy to Quattro. Just remember to Update > Channel > Stable after the final release!

Or install it fresh on a secondary machine using the ISO: https://iso.omarchy.org/omarchy-4.0.0.rc4-2.iso

Always take a backup of important data!

screenshot-2026-08-13_17-03-14-medium

Headline Features

  • Reimagine the entire desktop shell in Quickshell.
  • Move Omarchy internals from git to system packages for safely separating user modifications.
  • Offer dual-boot installation when free space is available (remember to turn off Bitlocker for Windows!).
  • Setup a machine for a new owner during install, so OEMs and gift givers can prepare a machine for others.
  • Allow for factory reset (Setup > Reset Computer) from new Quattro installs for safe resale/gifting of your machine.
  • Shrink the ISO by over a gigabyte (now under 6GB!).
  • Speed-up installation by +30% (sub-minute installs now possible!).
  • 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 plugin system and full ecosystem.
  • Add Omawrite, Omacut, and Omacalc as default applications.
  • Add a configurable default coding agent with instant launch shortcuts and crash diagnosis.
  • Add Solitude, Last Horizon, and Lupine as new themes.
  • Add window position saving per workspace for quick layout restoring.
  • Add much-improved clamshell operations for laptops used with external monitors.

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, do-not-disturb, deduping, and a replayable history: SUPER + SHIFT + ALT + , brings back the last ten notifications exactly as they were shown, including the ones do-not-disturb silenced — and popups on screen survive shell restarts (like the one every update performs), so critical alerts always make it across by @ryanrhughes and @dhh
  • 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 by @dhh and @ryanrhughes
  • 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/Fireworks usage stats and count from all the major harnesses by @ryanrhughes + @dhh
  • 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 on right-click
  • 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) by @ryanrhughes and @dhh
  • 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
  • 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
  • Add the Discord community and Herdr's keybindings viewer to the Learn menu by @dhh

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
  • Add Last Horizon and Solitude themes by @HANCORE-linux
  • Add Lupine theme by @bjarneo
  • 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
  • Bring back the classic Omakub background in Tokyo Night for early-Linux-adoption nostalgia
  • Add a Quattro background by @vulturetone plus a new winding-road launch background for Tokyo Night, and retire the backgrounds no longer worth shipping
  • Add a Pi theme based on the system theme by @ryanrhughes
  • Sync the active Omarchy theme to Claude Code by @MoizIbnYousaf
  • Add integrated corner controls (rounded/sharp) applied across Hyprland, notifications, lock screen, and menus by @ryanrhughes and @dhh
  • 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

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, opened as its own org.omarchy.agent app, and started in ~/Work when summoned from home so trust actually sticks 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 by @ryanrhughes
  • 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
  • Install ChatGPT as a real desktop app under Install > AI, dropping the web app from the defaults so the launcher can't show two identical entries by @dhh
  • Share files and folders over LocalSend through the desktop file chooser, instead of an fzf pick over a find of the whole home directory in a terminal 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 (±100px) window resizing tiers by @zaborowskimichal and @dhh
  • Add alternative media next/previous bindings for keyboards with only a play button by @dhh
  • Ship Herdr alongside tmux with matching keybindings, configuration, and hdl/hds/hdlm/hsl development-layout helpers 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
  • 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
  • Add laptop clamshell handling with idempotent scale recovery, internal-display toggle, and display mirroring by @dhh
  • Toggle Caps Lock by pressing both Shift keys by @tyvsmith

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 @ryanrhughes and @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 by @KazeTachinuu
  • 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

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
  • 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 — including unmasking a wpa_supplicant left masked from the iwd era 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
  • 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
  • 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
  • Greet the first login with a toast that opens the keybindings menu when clicked, and wait for NetworkManager to actually settle before deciding you have no Wi-Fi or offering an update by @dhh
  • Offer an AI diagnosis when a process crashes: systemd-coredump's journal stream raises a "Process crashed" toast, and clicking it briefs your default agent on the core with a diagnose-crash skill that covers symbolizing the backtrace and — only with your agreement, and after a duplicate search — reporting a confirmed Omarchy bug upstream by @dhh
  • Prune the package cache with paccache -rk2 as the first step of an update, before the snapshot so the space is actually reclaimed, and keeping one spare version so the offline downgrade path survives by @dhh
  • Detect NVIDIA GPUs from sysfs instead of lspci, which resumed a runtime-suspended discrete GPU out of D3cold and spent more than Hyprland's entire 1.5s config-load budget waking it — and classify by device ID, so pre-Maxwell cards stay off a driver that can't drive them and the Maxwell/Pascal parts the name regex missed are picked up by @dhh
  • Simplify the Quattro upgrade: follow the channel the machine is already on instead of dropping rc machines onto stable, leave the Omarchy 3 session running until the reboot that is the real cutover, shim the legacy Hyprland defaults from the on-disk backup rather than a network fetch of master, run the packaged firewall config so upgraded machines get the ufw-docker rules too, and clear the Hyprland error bar the swap used to leave on screen 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 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 when changing the drive password by @heymaikol
  • Align snapshot pruning with Snapper's retention (off-by-one) by @Pegorim
  • Force software cursors on nouveau by @stephentaylor-com
  • 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
  • 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
  • Switch Brave Origin from the beta to the stable release by @virtualabishek
  • Fetch high-res site icons for web apps instead of blurry favicons by @dhh
  • Resend the night light temperature until it sticks on a hyprsunset cold start
  • Fall back to the XDG MIME browser handler when xdg-settings names no browser by @ShiroKSH
  • Fit the About window to its rendered content so long values, custom fonts, and branding cannot wrap over the logo or clip — and open it at the size it last settled on by @28allday and @dhh
  • Fix background cycling for filenames with glob characters
  • 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
  • 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
  • Find UKIs through a restricted /boot on encrypted installs, fixing direct-boot setup and stale-UKI cleanup
  • 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
  • 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
  • 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
  • Repair the theme symlinks the state-move migration left dangling with an unexpanded literal ~, which cost btop, Helix, and VS Code their theme while the migration reported success by @shrijit37
  • Rebuild the boot images the Plymouth migration left stale, which kept encrypted machines falling back to an unthemed text LUKS prompt by @dhh
  • Fix --help being ignored for commands that resolve with arguments left over, like omarchy update aur --help by @EFrMG and @dhh
  • Drop the kms hook when the proprietary NVIDIA driver handles early KMS, cutting nouveau and ~100MB of its firmware from every initramfs on NVIDIA-only machines — existing images are rebuilt on upgrade by @matjam
  • Stop hybrid GPU mode queries and the menu's hybrid-GPU gate from hanging on a wedged supergfxd by @yashranaway and @dhh
  • Only offer real video capture devices as webcams, so IPU6 laptops stop opening a black overlay instead of their camera by @yashranaway
  • Treat LVDS and DSI panels as internal displays, so older laptops stop counting their own screen as an external monitor by @yashranaway

Thanks also to @AksharP5, @artfwo, @dalmasluca, @DataDave-Dev, @DiegoMirner, @flavorjones, @franciscoaccabral, @GebaRoanoke, @glafeara, @grantwiley, @heyssh, @JustMrMendez, @kevinmcconnell, @lukehsiao, @lukewalker2010, @markbus-ai, @meirdick, @monorkin, @richardlences, @RushiChaganti, @s-gato, @setiapam, @Vtorrealba, and @vivek7405 for fixes and polish to the new Quattro features during the beta itself.

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 is gone, and SUPER + SPACE now opens the Omarchy menu itself: a native, filterable command palette living inside the shell. Once the menu could search its own nested entries, there was no reason to keep two palettes with two shortcuts around — so one box now finds your apps and every Omarchy command, with instant response, and it's extensible through a JSONC file if you want to add your own entries.

menu

The app-launching side lost nothing in the merge: 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. And when you want a launcher scoped to just your apps, SUPER + ALT + SPACE opens exactly that.

launcher

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 put and omarchy bar move, and third-party widgets install straight from git with omarchy plugin add.

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

Pick your coding agent

Omarchy no longer picks a coding agent for you — it asks. Choose Claude Code, Codex, OpenCode, Pi, Oh My Pi, Gemini, Grok, Copilot, or Crush under Setup > Defaults > Agent, and it's lazy-installed the first time you use it. From then on, SUPER + SHIFT + CTRL + A (or just a in any terminal) summons your agent in its own window, started in ~/Work so trust actually sticks. The model-usage bar widget keeps an eye on your Claude and Codex spend, and when a process crashes, one click on the toast briefs your agent on the core dump for a diagnosis.

agent-picker

The new default apps

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

It's joined by Omacut, a dead-simple video trimmer. 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

Omacalc completes the trio: a dead-simple calculator replacing GNOME Calculator, on SUPER + CTRL + Q.

omacalc

And the defaults have been refreshed elsewhere too: Foot is the new default terminal, Tensaku replaces Satty for image annotation, Moonlight ships for Sunshine game/desktop streaming, and dua handles disk usage.

Install it for someone else

An install can now finish with no user at all. Deferred first-boot provisioning means you can set up a machine — for a family member, a colleague, a whole classroom — and hand it over untouched. On the first boot, the owner picks their keyboard, account, hostname, and timezone 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.

image

And when the machine comes back, Setup > Reset Computer hands it over again: the running root is swapped for a fresh clone of the @factory snapshot the ISO takes at install time, with machine identity, accounts, and fingerprint enrollments scrubbed — back to that untouched first-boot state.

This PR description is up to date as of 144f4d1e; 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
Comment thread default/chromium/extensions/whatsapp-theme/background.js Fixed
dhh and others added 13 commits August 7, 2026 02:38
Select-menu options gain an optional third field rendered under the
label, filtered alongside it, and returned with the selection. The
plugin picker uses it to show every plugin's id and act on the id the
selection hands back, replacing the duplicate-name label suffix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep only universal rules in AGENTS.md and point to per-task guides
for shell development, acceptance tests, visual verification, command
metadata, and install scripts. Fold the migration notes into
docs/migrations.md and replace .claude/CLAUDE.md with a root CLAUDE.md
importing AGENTS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leave agents/ ready for other agent artifacts like definitions and
prompts alongside the skills.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gin (#6598)

* Extract the Wi-Fi QR share card into its own omarchy.wifiqr panel plugin

omarchy-network-qr now leads with an iface/security/ssid meta line, so a
bare summon self-detects the connection and the plugin owns the whole
share flow. The network panel loses its overlay lifecycle: with no
centered card left inside it, the shadowed open/close collapses back to
the stock panel behavior, and the QR button just summons the plugin --
which a clone or third-party plugin can replace, like the speed test.

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

* Keep canceled QR and password runs from leaking into their replacements

Copilot review: the cancellation guards dropped in onExited while the
canceled run's collectors were still allowed to fire, so a stale stderr
could shadow a successful regeneration and a stale password could be
revealed under a new network's card. The guards now stay up until the
next run launches, good output settles any earlier error, and a bare
re-summon no longer inherits the previous card's SSID.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Split the end-user omarchy skill into topic guides

Move default/omarchy-skill to default/agents/skills/omarchy and break the
monolithic SKILL.md into on-demand topic files for Hyprland config, shell
plugins, theming, and hooks. Update the skill symlink wiring, relink
existing installs through a migration, and correct claims that had drifted
from the implementation: plugin hot-reload, terminal reload, menu
customization, refresh scopes, theme overlays, background locations, hook
timing, and the packaged (not git-managed) system directory.

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

* Add capture and contributing guides to the omarchy skill

Cover screenshots, screen recording, OCR text capture, and LocalSend or
Taildrop sharing, plus how to route bug reports, suggestions, and support
questions upstream with diagnostics and captures of the problem attached.

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

* Scope Hyprland reload guidance to the Lua config files

hyprsunset.conf and xdph.conf are read by separate processes, so hyprctl
neither applies nor validates them. Document restarting hyprsunset after
editing its config, including in the night light example.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Evaluate menu guards one run at a time

A second evaluation starting while one was in flight could not replace it.
Process ignores a command change until the next run and `running = true` is
a no-op while running, so setting them did nothing -- but clearing
`collected` first threw away the lines the running script had already
emitted. Its tail then landed as the entire result, and every id missing
from it went back to showing, since `when:` only hides a row on an explicit
false. That is how Setup > Defaults > Browser ends up listing browsers that
are not installed.

Queue the evaluation instead and run it once the one in flight lands, the
way provider enumeration already waits its turn.

* Answer repeated menu guard questions once per evaluation

The menu opens on the last evaluation's answers, so however long the guard
batch takes is how long a row can contradict the state it describes: stop a
recording and Screenrecord still offers to stop it, because the `pgrep` that
would hide it is queued behind fifty package lookups.

Almost none of that time is the questions, it is asking them one process at
a time. The shipped menu runs `omarchy-pkg-present` 54 times and
`omarchy-cmd-present` 23, and reads `omarchy-default-browser` once per row
in Defaults > Browser. Prepend a prelude that answers all of it inside the
one guard process, off a single package listing, bash's own PATH lookup, and
one capture per reader command. The captures are eager because `checked:`
reads them inside `$()`, where a lazy memo would not outlive the subshell.

Takes the shipped batch from 1.49s to 0.25s with identical answers for all
175 guards.

* Make the guard prelude answer exactly as the commands it stands in for

The prelude only helps if it is indistinguishable from the commands it
shadows, and it was not:

- `pacman -Q` resolves a name through what installed packages provide, so
  with gvim installed it reports `vim` as present. A set built from
  `pacman -Qq` sees only names, so `install.editor.vim` came back and
  offered to install what was already there. Build the set from provides
  too, and send version constraints, which no set can answer, to pacman.
- `omarchy-cmd-present` uses `command -v`, which finds builtins; `type -P`
  searches PATH alone and disagreed on every one of them.
- Shadowing a reader with a function caught far more than the plain
  `$(reader)` the rows use: `command -v omarchy-dns` got the function name,
  and `VAR=x omarchy-channel-current` got an answer captured without the
  variable. Substitute the captured value into the expression instead and
  leave every other form to run the real command.
- A reader that exits nonzero could take the batch down under a login shell
  with errexit set.

Also keep the results of a batch that was killed rather than finished, since
a row whose `when:` went unanswered shows, which is the failure this set of
changes exists to remove.

Costs 0.25s -> 0.33s against 1.49s before any of this, still with answers
identical to evaluating each guard on its own.

* Read every provide pacman reports, wrapped or not

`pacman -Qi` wraps a long list onto indented continuation lines whenever
COLUMNS is set in the environment, which the login shell the batch runs
under may well have done. Reading only the line that starts with `Provides`
dropped the rest: at COLUMNS=80 that is 537 of 856 provides on this machine,
which puts back exactly the "offers to install what is already there"
failure the provides lookup was added to prevent. Follow the continuation
lines instead.

The version-constraint case was also not testing what it claimed.
Interpolating the argument into the shadow's script text let `bash>=1` parse
as a redirection, so the shadow was handed `bash` and quietly agreed for the
wrong reason -- and left an `=1` file behind, which got committed. Pass
arguments as argv to both sides, drop the file, and wrap gvim's provides in
the stub so the parser is held to the format pacman actually emits.
* Persist on-screen notification popups across shell restarts

Mirror every popup to its own file under
~/.local/state/omarchy/notifications/ for exactly as long as it is on
screen: written when the toast appears, deleted when it expires, is
dismissed, is acted upon, or is replaced via freedesktop replaces_id.
On startup the directory is read back and still-valid popups re-shown,
so toasts survive the restart omarchy-update performs — critical
alerts, which never expire, always make it across.

Restored popups keep ids from the previous server generation, so the
replaces_id cleanup tracks them separately instead of mistaking a
fresh notification's reused id for a replacement, and the startup
restore only discards a persisted file when a live row with a
different timestamp has superseded it. Files are read back with awk
so a torn write can't glue itself onto the next file and take a valid
popup down with it.

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

* Close the remaining cross-generation id collisions in popup persistence

Notification ids restart from 1 with every server process, so an id
alone never identifies a notification across a shell restart. The
first round of fixes guarded row removal, but review (and a live
repro) showed the same collision biting everywhere else an id was
used on its own:

- Dismissing or clicking a restored toast resolved liveRefs by id and
  could dismiss, or fire the action of, an unrelated fresh
  notification, and archive its pending row. Restored rows now never
  resolve to a live object, and pending rows are matched by id plus
  timestamp.
- parsePopupFiles deduped files by id, so a fresh notification reusing
  a restored critical alert's id got that alert's file deleted as a
  "stale duplicate" on the next restore. Files are never deduped now:
  each one is a popup that was on screen, and the rare genuine
  leftover from a crash re-shows once and cleans itself up.
- The restore only skips an entry when a live row matches both id and
  timestamp (it is that entry); an id-only match shows both toasts
  rather than guessing which one to drop.
- A same-millisecond replaces_id update shares its predecessor's
  filename; the replacement's file is no longer deleted alongside the
  replaced row.
- A restored popup's reset lifetime is persisted as an absolute
  deadline, so a second restart judges it by the clock that actually
  governs its display instead of dropping it while still on screen.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…6539)

* Fix unbound XKBLAYOUT under set -u in the keyboard-layout migration

/etc/vconsole.conf only guarantees KEYMAP -- XKBLAYOUT is written by some
installers but not required, and vconsole.conf on a stock install may not
define it at all. omarchy-migrate runs every migration with set -euo
pipefail, so referencing the unset variable directly aborted the migration
run instead of just skipping a layout this migration doesn't care about.

Read it with a default-empty expansion first, then apply the existing
comma-strip separately -- keeps both failure modes (unset, and set with a
trailing keymap variant) handled explicitly instead of folding them into one
expansion that only covers one of the two.

* Survive a missing vconsole.conf in the keyboard-layout migration

Defaulting XKBLAYOUT fixed the unset variable but not the other way this
line takes the migration chain down. `.` fails when /etc/vconsole.conf is
not there at all, `&&` short-circuits, and the non-zero status leaves the
command substitution and kills the assignment under omarchy-migrate's
`bash -euo pipefail` -- the same abort, one branch over. The file is
optional enough that both other readers of it, omarchy_hooks.conf and
1781485962.sh, guard with `-f` first.

Run the echo unconditionally so the substitution reports its status
instead of the source's.

Read both paths from the environment, the way the zram migration already
does, and cover the layout cases plus both crashes with a test.

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

* Read the keyboard layout from vconsole.conf alone

Two ways the layout could come from somewhere other than the file, both
found reviewing the fix before it:

Sourcing does not clear an exported XKBLAYOUT, so on a machine that exports
one, a vconsole.conf that sets no layout -- or none at all -- left the
caller's environment deciding what the initramfs bundles. Unset it in the
subshell so only the file can answer.

Skipping the source when the file is missing, rather than letting the
substitution swallow the failure, also stops depending on errexit being
discarded inside command substitution. inherit_errexit takes that back and
the chain aborts again; nothing in Omarchy sets it today, but the other two
readers of vconsole.conf already check `-f` first and this now matches.

Test the layout list past its one Cyrillic entry, both orders of a
comma-separated pair, an exported XKBLAYOUT, and inherit_errexit. Each of
those catches a mutation that survived before.

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

* Drop the test scaffolding from the keyboard-layout migration

A migration runs once and then gets deleted, so a suite entry for this one
is upkeep with a short shelf life. The path overrides existed only so that
test could aim the migration at fixtures; with the test gone they are
indirection nothing exercises, so both go back to literal paths.

The fixes stay: check the file before sourcing it, unset XKBLAYOUT so an
exported one cannot answer for a file that sets none, and strip the keymap
variant before matching.

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>
…ents (#6603)

* Add agent usage collectors that write display-ready data files

One omarchy-agent-usage-scan-<agent> collector per AI coding agent prints a
complete display-ready usage record — identity, tier, status, rate limits,
and today/week/all-time stats. omarchy-agent-usage-update runs every
collector it finds and writes the records atomically to
~/.local/state/omarchy/agents/usage/, so anything that displays usage only
ever reads JSON from there.

The Claude collector absorbs what the shell previously did in-process:
transcript scanning, the stats-cache/history fallback, credentials parsing,
and the OAuth limits probe, now with a probe throttle and last-good limits
kept across network failures. The Codex collector is the existing scanner
reshaped to the shared record contract.

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

* Redo the model-usage plugin as omarchy.agents watching usage data files

The panel is now strictly a display. It discovers the JSON records that
omarchy-agent-usage-update maintains under
~/.local/state/omarchy/agents/usage/, watches them for changes, and draws
whatever appears — so adding an agent means shipping a collector, never
touching the panel. Marks resolve by convention (assets/<id>.svg with an
optional -light twin), the limits meters read a generic limits array, and
the per-provider QML adapters and in-plugin scanner scripts are gone.

Cross-device sync aggregation stays in the shell and keeps the snapshot
field names older versions wrote, so mixed-version fleets still merge in
both directions.

With the provider fan-out gone, the widget takes its real name: the plugin
id becomes omarchy.agents. A migration renames it wherever a user's config
mentions it — layout entries keep their settings and position, a disabled
widget stays disabled — then primes the data files once and drops the old
scanner cache. The migration test also drops a stale assertion that expected
migrations to restart the shell themselves, which c992cdf moved to
omarchy update.

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

* Address Codex review: synced-only tabs, limits retry, history fallback

Three data-availability gaps from review. An agent whose records only exist
in synced snapshots — a collector installed on just one machine — now gets
its tab by unioning the synced aggregate into the provider list, with rate
limits blank since those never travel. A Claude limits probe that reaches no
server at all writes retryAdvised into its record, and the shell honors it
with one 30-second retry instead of waiting out the full refresh interval,
restoring the old boot-before-DHCP behavior. And a machine with only
history.jsonl — no transcripts, no stats-cache — still reports today's
prompt and session counts.

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

* Address second Codex pass: history-only visibility, targeted retries

Today's prompt and session counts now count toward an agent's presence in
the bar, so a machine whose only Claude source is history.jsonl shows up
without waiting for limits. And the 30-second limits retry passes the
advising agent ids to the updater, so an outage at one provider no longer
puts every other collector on a retry treadmill.

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

* Drop omarchy-cmd-present jq guards from the agents migrations

jq ships in the default package set, which makes it a runtime invariant per
AGENTS.md — call it directly. The migration tests lose their now-unused
omarchy-cmd-present stubs with it.

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

* Drop the scan infix from the collector command names

Collectors are omarchy-agent-usage-<agent>; the updater skips its own name
when globbing them, and the update test proves it with a decoy.

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

* Keep the credential store out of the printed usage record

The Claude collector now reads .credentials.json once into three scalars —
the access token, its expiry, and the plan label — instead of passing the
parsed store around. The token reaches nothing but the Authorization header
of the limits probe, and only the plan label may travel into the record,
which is what CodeQL's clear-text-logging alert on the record print was
unable to see when the whole dict flowed through.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The panel resolves marks by walking assets/<id>-light.svg then
assets/<id>.svg, advancing on Image.Error. But the candidates binding is
re-evaluated whenever the provider objects are rebuilt, and a fresh array
identity with identical content reset the walk to the -light candidate.
Re-pointing source at a URL whose load already failed emits no
statusChanged, so the walker never advanced again and agents that ship a
single mark — Claude — fell back to the generic bar glyph on light
surfaces.

Key the reset on the candidate URLs instead of the array identity, and
defer the error advance one tick so stepping source from inside its own
status change doesn't trip the binding-loop detector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Faking HOME alone was never enough: the shell QML and the agent usage
updater read XDG_STATE_HOME and XDG_CACHE_HOME directly, so a test
quickshell inherited the session's real paths. The bar widget contract
test instantiated the agents widget, whose refresh ran the real
collectors against the empty fake HOME and wrote hollow "Waiting for
auth" records into the developer's real usage data files — hiding the
agents widget from their bar — while littering the real cache with
per-tmpdir scan files.

Point XDG_CONFIG_HOME, XDG_CACHE_HOME, and XDG_STATE_HOME under the fake
home in every test that boots quickshell with one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reopen wifi passphrase prompt after a wrong saved password

A failed first connection attempt leaves the network profile saved, so the
network shows up as known. Clicking it again reconnects with the stored
wrong PSK and fails with WifiAuthTimeout, but the inline passphrase prompt
only reopened on NoSecrets, leaving no way to re-enter the password short
of forgetting the network.

Treat an auth timeout on a protected network as a wrong saved passphrase
and reopen the prompt; connectWithPsk overwrites the stored PSK on submit.

Fixes #6582

* Scope the wifi passphrase reprompt to panel-initiated connects

Background auto-connect retries also fire connectionFailed; without a
gate they would pop the passphrase prompt open unbidden, stealing focus
and wiping a passphrase mid-entry when another network fails.

For the gate to see the failure, the action safety-net timer must
outlast NetworkManager's 25s supplicant timeout -- at 15s it cleared the
action state before WifiAuthTimeout arrived, so a wrong saved password
showed "Timed out connecting" instead of "Wrong password". Bump it to
30s.

Also share the one ConnectionFailReason map between the Model.js
helpers instead of building a second partial copy inline.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(network): drop the redundant rescan on the bar click

Opening from the bar ran open() and then a bare refresh(). open() already
triggers onOpenedChanged -> refresh(true), which defers the PHY scan by
disabling the scanner and re-enabling it from scanRestart. The bare
refresh() that followed defaults scanWifi to false, so it took the other
branch and set wifiDevice.scannerEnabled synchronously on the click frame,
undoing the deferral and stalling the open on NetworkManager's access-point
flood. It also double-started the DNS and band probes.

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

* fix(network): keep wifi rows QObject-free to prevent a delegate crash

wifiRow() embedded the WifiNetwork QObject in the row it returns, and those
rows are list-model data, so every delegate held a live QObject wrapper in a
var property. When NetworkManager churns the list -- a scan's access-point
flood, an AP disappearing -- the object can be destroyed while a delegate is
still incubating, and quickshell segfaults in QObjectWrapper::wrap_slowPath
on the dangling wrapper.

Project primitives only and resolve the backend object at action time via
the existing networkForSsid(). Both failNetworkAction() and
checkActionCompletion() already no-op on a null network, so a row whose
network has since vanished is handled the same way it was before.

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

* fix(bluetooth): keep device rows QObject-free to prevent a delegate crash

Same crash class as the wifi rows: scrollRows embedded the BlueZ Device
QObject in list-model data, so every delegate held a live wrapper in a var
property. Discovery churn -- a scan timeout dropping a device, an unpair --
can destroy the object while a delegate is still incubating, and quickshell
segfaults on the dangling wrapper.

Project primitives for both the scroll rows and the connected rows, and
resolve the backend object by address in deviceFor() for the click actions.
The keyboard flow already went through deviceAt(), which reads the live
device arrays directly rather than model data, so it is untouched.

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

* fix(network): guard row disconnects against a vanished network

Row activation resolved the WifiNetwork with networkForSsid() and passed the
result straight to disconnect(), which falls back to connectedWifiNetwork
when handed null. A row is a primitive snapshot, so scan churn can remove its
backing object while the row is still on screen -- activating it then tore
down whatever happened to be connected at that moment rather than doing
nothing.

Route both row paths through disconnectRow(), which resolves first and only
acts when the row still maps to a live network. disconnect() keeps its
fallback for callers that mean "drop the current connection".

Also covers the bar-click open path, which had no regression: the suite
already asserts against Panel.qml source, so assert the closed branch calls
open() alone and never a second refresh().

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

---------

Co-authored-by: shrijit <shrijitsrivastav@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
dhh and others added 24 commits August 17, 2026 06:09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Re-encode over-encoded theme backgrounds

Several backgrounds shipped at quality 95-100, which buys nothing visible
on a wallpaper but costs 20 MB. Re-encode those at quality 85, leaving
resolution untouched -- every file keeps its original pixel dimensions.

Only files stored above quality 90 are touched, and only when the result
saves at least 15% and stays above 38 dB PSNR against the original. The
34 backgrounds already stored at quality 85 or below are left alone
rather than pushed through another lossy generation for a few hundred KB.
osaka-jade/2-shaded-entrance is skipped for that reason: nothing clears
both bars. ristretto/2-coffee-beans is re-encoded at 90 instead of 85,
where 85 fell below the PSNR floor.

Theme backgrounds drop from 107 MB to 87 MB.

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

* Store nord's night-hawks background as a palette PNG

The file uses only 8 distinct colors but was saved as truecolor RGB,
costing 1.8 MB for an image an indexed palette stores in half the space.
Converting to a palette PNG is lossless: same dimensions, and zero pixels
differ from the original.

1782 KB -> 909 KB.

The other palette-eligible PNGs are already indexed, and re-compressing
them with ImageMagick only makes them bigger, so they are left alone.

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

* Recompress theme PNGs losslessly with oxipng

Every background PNG was left at its authoring tool's default deflate
settings. Running oxipng over them re-packs the same pixels: all 31 files
decode bit-for-bit identically (AE=0) at unchanged resolution, for 981 KB.

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

* Store photographic backgrounds as JPEG instead of PNG

Four backgrounds were shipped as PNG despite being photographs and
painterly illustrations, where lossless coding buys nothing the eye can
see. Re-encoding them at the same q85 used by the other backgrounds keeps
every pixel dimension and stays above 40 dB PSNR, for 3.3 MB.

The remaining PNGs stay PNG: JPEG is larger for the dot patterns and the
flat-shaded pieces, and 0-launch.png genuinely uses its alpha channel.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Give scratchpad a Quake-style presentation

* Keep the Quake scratchpad from restyling every window

The presentation was bought with global decoration defaults: rounding went
0 -> 5 and both shadow and blur were switched back on for every window on
the system, undoing 935283c. Blur in particular is close to invisible
here, since every window is already tagged 0.985/0.96 opacity and no layer
rule asks for it, so it was GPU load on every frame for almost nothing.

Put the globals back and scope the rounding to the scratchpad with a window
rule, the same way popped windows already get theirs. The inset, dim_special
and the slide carry the effect on their own. dim_special only applies while
a special workspace is open, so it stays.

Also drop the four workspace-rule booleans that only restated Hyprland's
defaults, fold the stale scratchpad rows in the hotkey table into the new
ones, and give the binding assertions their own fixture instead of borrowing
the bar-panel one.

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

---------

Co-authored-by: Omabot <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Open the scratchpad with the default agent already in it

on_created_empty fires when the special workspace is created empty, so the
agent starts the first time the console drops down instead of at boot, and
comes back on the next open if you close it.

The exec rule pins the workspace rather than trusting the spawn to inherit
it: Hyprland only tags a process with its origin workspace while
misc.initial_workspace_tracking is on, and we turn that off.

Nothing to guard for a missing default agent. Omarchy picks none for you,
and omarchy-agent exits without opening a window when none is set, so the
scratchpad just opens empty until one is chosen.

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

* Move the console into its own file and size it to half the screen

The scratchpad's presentation was scattered through looknfeel: a dim in the
decoration block, a workspace rule below it, two animation leaves further
down again. Gathered into qconsole.lua, where the whole console is one
readable thing.

Sized to half the screen while it moved. A window rule cannot do that: its
size expressions resolve once, when the window maps, so rescaling the
monitor afterwards leaves a console that is no longer half of anything.
Gaps are re-applied by the layout, so the console is sized by the gap left
underneath it, recomputed from the monitor whenever the layout changes.

Monitor dimensions come back in physical pixels while gaps are logical, so
the scale comes out before the reserved area comes off. That arithmetic is
the whole trick, and the test pins it at 1x, 2x and 1.5x.

The test runs lua with an explicit "-". Bare `lua <<EOF` reads stdin as a
REPL and exits 0 even after an error, which would leave its assertions
unable to fail.

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

* Only rewrite the console rule when its size actually changes

Refitting replaces the rule in place rather than stacking a new one, so
there was no leak, but each write still schedules a monitor and window state
refresh and monitor.focused fires on every hop between screens. Remember
what was last written and skip the write when the number has not moved.

Also say out loud that the scale guard is what keeps the arithmetic below it
safe: a monitor handle that has outlived its output answers nil to every
field, and a layout change is exactly when that happens.

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

* Drop the active window border inside the console

The gradient border marks which window has focus, which the console does not
need: it is only ever focused while it is open, and the dimmed workspace
behind it already sets it apart. On a single agent terminal the highlight
just reads as a frame around the panel.

no_border on the workspace rule pins the border to 0 at workspace-rule
priority, so it applies to whatever ends up in there without touching the
global border.

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

---------

Co-authored-by: Omabot <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Decode mixed UTF-16 clipboard text

* Harden UTF-16 clipboard detection

* Finish UTF-16 decoder hardening
* Print the OEM Windows product key from firmware

Machines that shipped with Windows keep the OEM key in the ACPI MSDM
table. `omarchy windows license key` reads it with strings, then cat.

* Rename the firmware key command to omarchy-windows-key
* Decode webp in the shell

The background and the lock screen are drawn by Quickshell, so they
decode through Qt, which ships handlers for png, jpeg and gif but not
webp. QImageReader answers "Unsupported image format" and the layer
comes up blank. Any third-party theme shipping a .webp background hits
this today, even though every path that goes looking for a background
already globs the extension.

qt6-imageformats supplies the missing plugin for 71 KB downloaded. Its
one new dependency of substance, libwebp, is already on every machine
by way of libvips.

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

* Store theme backgrounds as webp

WebP codes both of the things these backgrounds are made of better than
the formats they were in: the photographs, where its lossy mode is worth
a third or more over JPEG at matched quality, and the flat art and dot
patterns, where its lossless mode undercuts an oxipng-packed PNG.

28 of them become lossless webp and decode bit-for-bit identically
(AE=0), so the dot patterns and flat-shaded pieces carry no quality
question at all. That includes 0-launch, whose alpha channel comes
through intact. The other 51 are photographs held to the same 38 dB
PSNR floor as the JPEG pass, landing between 38.0 and 54.6 dB. Every
image keeps its exact pixel dimensions, for 29.8 MB.

Each one is encoded from the original as it stands in quattro rather
than from the file the earlier commits produced, so nothing picks up a
second generation of loss on the way here.

13 stay JPEG. WebP is plainly larger for most of them, and three are
grainy enough that its filter smooths the grain instead of coding it:
PSNR plateaus near 34 dB however high the quality goes, well under the
floor.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Switch DNS providers without a password prompt

The network panel and the menu run omarchy-dns from a process with no
terminal, so require_root reached for pkexec and put a polkit password
prompt in front of what is meant to be a one-click toggle.

Grant %wheel passwordless sudo for the three stock providers and take
that path whenever the grant covers the invocation. Custom stays out of
the grant: it points the machine at servers the caller supplies, and it
already runs in a terminal that can ask.

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

* Pick the elevation path without asking sudo

The `sudo -n -l` probe answered the wrong question. It reports whether a
command is permitted, not whether it is passwordless, and the %wheel rule
every Omarchy install ships permits everything -- `sudo -n -l /usr/bin/rm
-rf /tmp/x` exits 0. So the probe passed for Custom too, and the exec
below it ran `sudo -n`, which fails outright with no terminal and no way
back to pkexec.

Decide from what the sudoers rule actually says instead: sudo when there
is a terminal to type into, or when the resolved path and the provider
are both ones the rule names. Everything else keeps going through polkit.

Pin a root-owned PATH once elevated, too. `omarchy dev link` puts a
user-writable checkout ahead of sudo's secure_path for every command, so
a passwordless grant on a script that resolves nmcli, tee, and install
through PATH would otherwise hand root to whoever can write there.

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

* Keep users outside %wheel on the polkit path

The rule grants %wheel, so path and provider alone do not mean sudo will
take it. A user outside the group was sent to sudo anyway, and with no
terminal to answer the prompt that is a dead end -- polkit at least
offers to authenticate as somebody else.

Two holes in the test alongside it: it accepted any file containing the
expected rule, so a second, argument-free line would have widened the
grant unnoticed, and run as root it would have sailed past the stubs and
rewritten the host's own DNS config.

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

* Elevate the system install, whatever copy was invoked

The rule names /usr/bin/omarchy-dns, so a dev-linked checkout handed sudo
a path nothing could match and fell back to a polkit prompt. Re-exec the
packaged path instead: the privileged half is the system install
everywhere, the grant matches everywhere, and the path comparison and the
PATH pinning that existed to work around the checkout both go away.

Dev-linked checkouts run their own unprivileged half and the installed
one as root, which is the trade for not carrying a second code path.

---------

Co-authored-by: Omabot <david@hey.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
grant_covers re-implemented etc/sudoers.d/omarchy-dns in bash -- one of
the three providers, and %wheel -- but never asked whether the rule was
installed. It ships in the etc/ tree that omarchy-settings copies, so
every machine still on an older settings package answers yes to a grant
it does not have. require_root then execs into sudo with no way back,
and the panel's one-click toggle dies on a password prompt it has no
terminal to show.

Ask sudo instead. `sudo -l` alone reports whether a command is
permitted, which the blanket %wheel rule answers yes to for everything,
but the long listing prints the matched entry's tags -- !authenticate is
the grant and nothing else. It runs nothing, and under -n it prompts for
nothing, so a machine without the rule falls through to polkit and gets
a prompt on screen.

The provider list and the wheel check go away with it; sudo owns that
policy now, and it stays right if the rule is ever edited or removed.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
t3code-bin is in the Omarchy repo now, so the menu can offer it the way it offers Cursor and Grok Bot: install the package, then launch the desktop entry it ships.

The mark is a trace rather than a download. T3 publishes no monochrome SVG — the app icon is a black rounded tile with the letters knocked out of it, and a tile flattens to a solid square once the menu recolors every path with the theme foreground. Tracing the lettermark out of that icon keeps the silhouette that actually reads.

The font is package-owned, so the glyph reaches a desktop through an omarchy-settings release rather than omarchy update. Until that release lands, a pulled checkout draws the entry with no icon.

🤖 Generated by Opus 5 in Claude Code.
* Add Remove > AI for the apps Install > AI offers

Install > AI grew five entries and Remove grew one: Dictation, sitting on its own at the top level. Everything else installed from that menu had to come back out through Remove > Package by name.

Mirror the install tree instead. Dictation moves under the new AI submenu where its installer already lives, and ChatGPT Desktop, Grok Bot, LM Studio, Ollama and T3 Code get removers beside it. Each entry is conditional on the thing actually being installed, so the submenu only ever lists what is there.

What each remover deletes was read off a machine that had all five installed and launched, not guessed. That matters most for T3 Code, which bootstraps the agents it drives: ~/.claude.json, ~/.grok, ~/.npm and ~/.local/share/opencode all appear the first time it runs, and all of them outlive it, so it takes only ~/.config/t3code and ~/.t3. Grok Bot is the same trap in miniature -- ~/.grokbot is its own, ~/.grok belongs to the Grok CLI.

Ollama drops every acceleration variant rather than the one the installer happened to pick, and disables the service before the package, since that is what holds the models open.

🤖 Generated by Opus 5 in Claude Code.

* Remove only what these apps own, and only where removal works

Three defects from an independent review of the previous commit.

ChatGPT Desktop was deleting ~/.cache/codex-runtimes, which belongs to the Codex CLI rather than the desktop app: the `codex` binary resolves its runtime and plugins out of that directory, and it ships in a package this remover does not touch. Removing the desktop app took the CLI's prepared runtime with it, leaving a separate, still-installed tool to rebuild it -- and unable to, offline.

Ollama's row appeared whenever the `ollama` command existed, but omarchy-pkg-drop removes exact package names. With ollama-bin, ollama-git or a hand-built binary the entry offered a removal it could not perform: the service went down, /var/lib/ollama and ~/.ollama were deleted, and it reported success with the program still installed. Every acceleration variant depends on the base package, so testing for that package covers each one the installer can produce and nothing it cannot remove.

LM Studio keeps its models under a relocatable home, and ~/.lmstudio-home-pointer is the only record of where they went. The remover deleted the pointer and the default path, so a user who had moved their models kept every one of them while being told they were gone. Read the pointer before deleting it, and refuse one aimed at / or at the home directory itself, since following it there would take everything.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
* Use (( )) for the numeric argument test

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

* Drop the quotes on a variable inside [[ ]]

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

* Use omarchy-pkg-drop instead of raw pacman -Rns

omarchy-pkg-drop already filters to installed packages, so the
2>/dev/null || true suppression is no longer needed.

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

* Drop defensive checks around default-set commands

ttfx, imagemagick, and networkmanager are all in the default package
set, so their commands are runtime invariants and should be invoked
directly. Removing the nmcli guard also removes the degraded wifi
fallthrough that only ran when nmcli was missing.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The shell already shows a "Launching Windows…" OSD from the moment the desktop entry is activated until the RDP window appears, so the notification duplicated feedback the user is already looking at. The failure notification stays: nothing else reports a VM that never came up.
* Add a clock format with live seconds

Right-clicking the clock now reaches "Thursday 09:39:23" and its AM/PM twin, and the widget's SystemClock ticks once a second only while a format that prints seconds is showing — every other format keeps the minute precision it had, so nobody pays for a repaint a second to read a label that changes once a minute.

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

* Read an unterminated literal in a clock format as text

Qt reads an opening quote with no closing one as a literal running to the end of the format, so "HH:mm 'sec" prints "09:39 sec" and never a second count — but the seconds test stripped only balanced quotes, saw the s, and put the widget on a per-second tick for a label that changes once a minute. The wiring assertions went the other way: each passed while the feature was broken, so hard-coding showsSeconds to false, dropping the label's onDateChanged, or commenting the precision line out and leaving the text behind all shipped green. Comments now come out of the source before it is matched, and both halves of the tick are asserted.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
* Replace Gemini coding agent with Antigravity

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Remove the dead Gemini mise wrapper in the Antigravity migration

Remove Preinstalls no longer lists gemini, so the wrapper Omarchy created
would have stayed in ~/.local/bin with nothing left to clean it up.

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

* Install Antigravity when it is the default a Gemini user is migrated onto

The opt-out check skipped the install but the rewrite ran anyway, so anyone
who had removed the preinstalls was left with a default agent naming a
command that is not there.

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

* Fix Antigravity skill provisioning and Gemini wrapper migration

- Wires Omarchy's default skills into Antigravity by linking them to ~/.gemini/config/skills/ in bin/omarchy-provision-user and migrations/1786719479.sh.
- Fixes the Gemini wrapper migration in migrations/1786719479.sh to recognize and remove wrappers containing either `mise use -g "gemini"` or `mise use -g --quiet "gemini"`, while leaving hand-written wrappers intact.
- Adds regression tests for both skill provisioning and wrapper removal in test/shell.d/default-agent-test.sh and test/shell.d/provision-user-test.sh.

* Stop the provisioning test from retheming the session it runs in

The test ran the real omarchy-provision-user, which sources install/user/all.sh and so reached omarchy-theme-set: hyprctl reload against the live compositor, gsettings against the live desktop, and a global Node install, none of which the skill symlinks it asserts need. Its mocks for omarchy-done and omarchy-refresh-applications were shadowed anyway, because provisioning prepends $OMARCHY_PATH/bin ahead of them, so stubbing the install suite at its own path is what a mock cannot do here. The exit status is checked rather than discarded: the assertion held even when provisioning died outright, because the symlinks are made twenty lines before the suite runs.

* Match the Gemini default and wrapper the way Omarchy writes them

The migration decided both questions differently from the code that owns them. It read the default agent with grep -qxF, while omarchy-default-agent takes the first line through read, so a padded "  gemini  " that the launcher still resolves was left naming an agent the launcher no longer supports. The wrapper it deletes was matched anywhere in the file, so a hand-written one that only mentions the installer's line in a comment went with Omarchy's own. Reading it the launcher's way and anchoring the match settles both against whoever wrote the file. The skills loop guards its glob the way migrations/1786539345.sh does, so an empty source cannot leave a symlink named "*" behind a migration already marked complete.

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

* List Antigravity among the skill directories

The manual named Claude Code, Codex, Pi and the generic location; provisioning now links ~/.gemini/config/skills too.

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Omabot <omabot@omarchy.org>
fuchsblau and others added 5 commits August 22, 2026 13:43
With the default scale = "auto", sync_internal_scale read the config,
rejected "auto" as non-numeric, fell back to the hardcoded default 2,
and force-applied it whenever the compositor's auto resolution differed.
Since the script runs from omarchy-system-wake after every idle cycle,
the panel flapped between 2 and auto's own value (1.5666667 on a 198 DPI
panel) on every wake/reload pair.

A config without a usable number -- "auto", or an expression only
Hyprland's Lua can evaluate -- delegates the scale to the compositor:
whatever it resolved for the enabled panel is the configured scale, so
there is nothing to correct. Recovery of a disabled panel is unchanged
and still re-enables it with the remembered scale, falling back to the
historical default 2.

Fixes #7265. Also the scale-revert half of #7301.


Claude-Session: https://claude.ai/code/session_01L4Z6GimYhR1Kpsir24VAPF

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Given Finley's confusion
…usly (#7769)

* Switch back to the packaged quickshell now that 0.3.1 kills synchronously

Omarchy shipped the quickshell-git build for a single fix: 0.3.0's `kill` returned before the instance had exited, so the kill loop in omarchy-restart-shell could race a dying shell. Upstream 0.3.1 ships that fix, which makes extra/quickshell the better package to be on again — signed, versioned, and not rebuilt from a moving branch on every update.

The migration swaps unconditionally instead of first checking which version the mirror offers. A machine left holding quickshell-git while the shipped package list names quickshell has no way to reconcile the two: omarchy-reinstall-pkgs installs that list with --needed, which does not skip a name that is not installed, and the conflict it then walks into has no answer under --noconfirm. A mirror that is briefly behind installs 0.3.0 instead and the next upgrade carries it to 0.3.1, which is much the cheaper way to be wrong.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Codex XHigh <codex@openai.com>

* Drop the quickshell version note from the shell restart loop

The comment qualified the kill loop as needing 0.3.1 or newer, but omarchy-restart-shell ships in the same package upgrade that brings quickshell along, so a machine running this code already has the version the loop depends on. The caveat could never be false where it was read, which left it as version archaeology rather than something the code could not say for itself.

🤖 Generated by Opus 5 in Claude Code.

---------

Co-authored-by: Codex XHigh <codex@openai.com>
* Only offer Update > Extra Themes when there is one

omarchy-theme-update pulls the themes under ~/.config/omarchy/themes that came from a git clone, so on a machine that has never installed one by hand the row opens a terminal that prints nothing and closes. Guard it with the same predicates the command itself applies, since a row that shows over a symlinked theme or a worktree's `.git` file is the same dead end in a narrower shape, and pin the two to each other in the guard test.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Extract the Extra Themes guard into omarchy-theme-extras

The row's `when:` and omarchy-theme-update each carried their own idea of which themes came from a git clone, and the two only matched because a test held them together. Name it once instead: omarchy-theme-extras lists those directories and exits nonzero when there are none, so the row asks exactly the command its action runs. Living in a script also puts the glob out of reach of whatever shopt a login shell left set for the guard batch.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.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.