Commit Graph
654 Commits
Author SHA1 Message Date
David Heinemeier HanssonandClaude Opus 5.5 1ac31e55ae Never leave the desktop without a wallpaper after a theme intro (#15000)
A theme switch that plays an intro gave the shell the new still only when
the intro failed. The shell kept the old theme's path, which the theme swap
deletes, and loaded it again when OWE handed the desktop back, leaving it
empty. The new still is now handed over either way.

The shell also re-reads the background link whenever OWE hands the desktop
back or a wallpaper fails to load, falling back to the theme's first
background when the link itself is broken.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:42:00 +09:00
David Heinemeier HanssonandClaude Opus 5.5 754d2d04b3 Only recover a session lock no other locker may hold (#15020)
Hyprland lets any client take over a held lock, so a second shell started
beside the desktop judged the real shell's live lock stranded, took it, and
then exited, leaving the session behind the failsafe while the real shell
still reported itself secure. Recovery now waits while any other Quickshell
or hyprlock runs, and gives up if one never exits.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:26:49 +09:00
David Heinemeier HanssonandClaude Opus 5.5 a98e55eb91 Pick window transparency from Appearance > Window Transparency, and a Glassy bar background (#14991)
* Pick window transparency from Appearance > Window Transparency

Transparent, the default, keeps today's windows. Opaque turns the windows
Omarchy makes transparent solid. Glassy is the frosted glass Starship
(#14726): its blur behind every see-through window and shell surface, and a
see-through, blurred background in the theme's terminal files, since foot
turns compositor blur down and a Hyprland opacity rule fades text.

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

* Leave window transparency to Appearance when animations are off

Turning animations off no longer makes windows opaque. VM installs start
with opaque windows instead, and a migration keeps them opaque for anyone
who already has animations off.

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

* Add a Glassy bar background, under Menu Bar > Bar Background

Menu Bar > Bar Background picks Opaque Bar, Glassy Bar or Clear Bar, and
Indicators becomes Bar Indicators to match. Glassy keeps the theme
background faintly and Hyprland blurs what is behind it, with the same blur
as Glassy windows.

omarchy bar background opaque|glassy|none|cycle sets it and prints it when
given nothing. omarchy bar transparent true|false|toggle stays for
compatibility, as none and opaque. Double-clicking the bar cycles through
the three backgrounds the same way, from clear to glassy to opaque.

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

* Stop when a saved corner choice can't be removed

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

* Make btop follow glassy windows

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:21:00 +09:00
David Heinemeier HanssonandClaude Opus 5.5 4df545905d Pick window corners from Appearance > Window Corners (#14990)
* Expect Appearance before Toggle under Trigger

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

* Read the default workspace layout once for all the menu rows that ask

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

* Pick window corners from Appearance > Window Corners

Theme Corners leaves them to each theme. Sharp and Rounded override every
theme through a Hyprland toggle, with Rounded using Solitude's corners.

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

* Keep the corner choice when saving a new one fails, and test the load order

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:06:54 +09:00
David Heinemeier HanssonandClaude Opus 5.5 5ec8bd300b Pick the bar clock format from Appearance > Menu Bar > Indicators > Clock
Every preset the clock cycles through gets its own row, labeled with how
it reads, and the current one is checked. omarchy-bar gains get <id> <key>
and a bare position to read the settings the rows compare against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:10:33 +09:00
David Heinemeier HanssonandClaude Opus 5.5 18e059d9a4 Show the remote session indicator only during a remote session (#14981)
Hovering the indicators revealed it dimmed alongside the ones that can be
switched on, though there is nothing to do with it there. An indicator can
now opt out of that reveal, and remote session does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:58:19 +09:00
David Heinemeier HanssonandClaude Opus 5.5 d1a614ce66 Decide lock requests from the current lock state (#14978)
The locked binding once read true after an unlock, though the request,
the session lock, and its secure state were all false. A lock before
suspend then took the session for locked and did nothing, and the
machine slept unlocked. Lock requests, the status reply, and stranded
lock recovery now read those three values directly.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:37:45 +02:00
1498718969 Add a floating mode with titlebars to the workspace layout cycle (#14482)
* Let the workspace layout test's Lua assertions fail the suite

Lua 5.5's standalone interpreter exits 0 when the script that raised the error came in on stdin, and exits 1 for the same error in a script file. The block here was piped in through a heredoc, so every assertion in it reported as passing however wrong the value was: changing the expected layout to one the toggle never writes still left the file green.

* Add a floating mode to the workspace layout cycle

Super + L cycled a workspace between dwindle and scrolling. It now carries on into floating, where every window on the workspace floats.

Floating cannot be a third `layout` on the workspace rule. Hyprland has no layout by that name and accepts one anyway: `hl.workspace_rule({ layout = "floating" })` returns ok and silently leaves the workspace on dwindle, exactly as a misspelt name does. So the mode floats the windows themselves, through the same `float` dispatcher SUPER + T uses.

Ending the mode has to tile the windows the mode floated and only those, so each one is tagged with `omarchy-mode-floated`. A window already floating when the mode reaches it -- an app with a float rule of its own, a dialog, one popped out with SUPER + T -- is left unclaimed and is still floating when the mode ends. The tag lives on the window rather than in a table here: it dies with the window, follows it between workspaces, and survives the config reload that plugging in a monitor performs, none of which a table on this side would do. A pinned window is left alone entirely, because tiling one unpins it as well.

Windows arrive on a workspace three ways and a window rule only covers one of them, so the mode watches events: window.open for what opens there, window.move_to_workspace for what is carried in, and the same event for what is carried back out, which gives up the floating the mode gave it rather than staying floating on a workspace that tiles. Every tiled window is claimed before any of them is floated, because floating one member of a group floats the rest, and claiming as it went would find those already floating and leave them stranded.

The mode is read back from the saved file rather than from the compositor. Hyprland reports a workspace's tiled layout and floating is not one, so a floating workspace still answers dwindle or scrolling; trusting that answer would skip a step of the cycle. A file saved before floating existed names a layout rather than a mode and falls through to what Hyprland reports, so the cycle keeps working and the next press rewrites it.

Two faults in the old script had to go first, because saving the mode makes both persistent. Named workspaces report a negative id, which passed the `^-?[0-9]+$` guard, and Hyprland reads a leading "-" as a selector relative to the current workspace: pressing Super + L on a named workspace reconfigured workspace 1 instead, and would now save a file that did it again at every login. And the saved state was written under $HOME/.local/state while the Hyprland side reads XDG_STATE_HOME, which also had to reach package.path: the loader lists that directory but loads the files by module name, so a state home anywhere else listed the saved layouts and then loaded same-named ones from the home directory.

Saving is checked too. The mode reaching the compositor and the mode reaching disk are separate things that can fail separately, and a save that failed silently would leave the next press reading a file that never got written, cycling between two modes and never reaching the third.

The guard against named workspaces stops new files being written, but an earlier version already wrote them, and they go on being loaded at every login. A migration removes the saved layouts named for a negative id.

The keyword fallback is gone. `hyprctl keyword` refuses a Lua configuration outright -- "keyword can't work with non-legacy parsers. Use eval." -- and exits 0 while doing so, so as a fallback it neither worked nor reported that it had not. A failed apply now says so and saves nothing, rather than announcing a layout the screen does not show and restoring it at the next login.

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

* Add titlebars, edge snapping, and a window Shelf to floating workspaces

Floating workspaces become a traditional desktop that works with every
theme. Windows get themed titlebars from the omarchy-hyprland-titlebars
plugin, which is loaded only while something floats. Dragging a titlebar
to the left or right edge snaps the window to that half, and dragging it
to the top or double-clicking maximizes it. The window floating mode
places is sized, centered, and cascaded, and floating workspaces focus
on click.

Super + M, or the minus button, sets a window aside on the Shelf, which
works from any workspace and restores the window onto the current one
in that workspace's layout. Float All Workspaces makes floating the
default while Super + L still sets a single workspace's layout.

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

* Bring resize on border to tiling too

* Fix floating mode cleanup, fullscreen windows, and failed Shelf notes

Turning Float All Workspaces off with no workspace layouts left behind
started nothing to give the floated windows back. A window fullscreen
when its workspace began floating now floats once it leaves fullscreen.
A window whose Shelf note cannot be saved stays where it is, and the
titlebars read the theme from where omarchy-theme-set writes it.

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

* Place the Shelf on the bar once, instead of on every minimize

Putting it back on each minimize kept a Shelf the user moved where they
put it, but re-added one they had removed. A migration places it once for
existing bars; Super + Alt + M opens the Shelf either way.

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

* Raise titlebars off the window background

A titlebar in the theme's background color ran into the window under it.
It takes the theme's lighter background, the raised surface every theme
defines, and the selection color on themes that make the two the same.

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

* Drop minimizing and edge snapping from floating workspaces

A floating workspace is now titlebars with maximize and close, and nothing
else. The Shelf (its bar widget, panel, commands, notes, Super + M and
Super + Alt + M) goes with the minimize button, and the titlebar plugin's
edge snapping is left off, along with the shell's snap preview.

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

* Stop describing the titlebar package as snapping

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

* Name web app windows in their launchers

Chromium names a web app window after its URL, and titles it with the page
title or just the domain. StartupWMClass ties the window to its launcher,
so the titlebar plugin can show the launcher's Name instead: WhatsApp
rather than web.whatsapp.com. New web apps, the shipped ones, and existing
launchers (through a migration) all carry it.

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

* Drop the titlebar package comment from the base list

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

* Show the workspace layout in the bar and cycle it on click

The focused workspace's mark is a square for dwindle, a triangle for
scrolling, and a circle for floating, and clicking it cycles the layout
as Super + L does. Both layout toggles ask the bar to read the new mode.

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

* Show scrolling as a circle and floating as a triangle

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

* Move the look of the desktop to Trigger > Appearance

Menu Bar, Battery Percentage, Workspace Layout, Window Gaps, Animations,
and 1-Window Ratio change how the desktop looks rather than switching a
behavior, so they leave Trigger > Toggle for their own menu.

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

* Choose a default workspace layout instead of floating everything

Trigger > Appearance > Workspace Layout sets the layout of every workspace
without one of its own: Tiling, Scrolling, or Floating. A tiled default
is Hyprland's own layout, and a floating one is the floating mode. It
replaces Float All Workspaces, which also overrode and set aside the
layouts workspaces had been given with Super + L.

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

* Title the workspace layout menu Default Workspace Layout

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

* Use the animation icon for Appearance > Animations

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

* Open clipboard history from Trigger

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

* Leave edge resizing to looknfeel on floating workspaces

looknfeel now resizes from borders everywhere, so floating workspaces
only need to switch pointer focus to click.

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

* Convert saved workspace layout rules to modes

Released versions saved a workspace's layout as a plain workspace rule,
which names no mode, so a floating default would override a workspace
set to scrolling, and the bar and Super + L would misread it. A migration
rewrites those files as modes.

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

* Cycle workspace layouts backwards with Super + Alt + L

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

* Test the Trigger > Appearance split and Clipboard History placement

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

* Decode escaped web app URLs before naming their windows

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

* Describe the workspace widget's layout mark and click

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

* Let a workspace cycled onto the default follow it again

Once Super + L gave a workspace a layout of its own, it kept it for good,
so a later change of the default never reached it. Cycling onto the
default layout now forgets the workspace's own instead of saving it.
Without a saved default, the default is Hyprland's own layout, which
the user's looknfeel may have set.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 08:20:45 +02:00
d74d6ea249 Build the calendar month grid in UTC (#10476)
* Build the calendar month grid in UTC

The grid walked a cursor at local midnight and stepped it with setDate(getDate() + 1). Chile starts DST at 24:00 on the first Saturday of September, so local midnight of 6 September 2026 does not exist, and Qt's V4 engine resolves a missing local time backward -- onto 5 September 23:00, the same calendar date, which the next iteration reads again. September 2026 draws 30 31 1 2 3 4 5 and then 5 6 7 8 9 10 11, and every later date sits one weekday early. Cuba, Lebanon, the Azores and Egypt change over at 00:00 too and break the same way in their own transition month; where the gap lands on a leading padding cell instead, the whole six-week grid shifts by a day with no duplicate in it.

Node resolves the identical gap forward, so the suite has never seen this: running the shipped monthGrid under Node across all 598 tzdata zones for 2020-2035 reports nothing, while the same code under the QML engine breaks 402 grids. A sequence of civil dates never needed a wall clock, so it walks UTC, where every day is 24 hours long, and the test pins that by building the grid with a Date whose local constructor and accessors throw.

stepMonth reads a month back off the same kind of date. No zone puts a midnight gap on a first of the month, so it is not broken today, but it moves with the grid rather than waiting for a tzdata release to make it a second bug.

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

* Name the calendar heading's month from local noon

The heading built a local midnight on the first of the month, and that midnight does not always exist: Cuba started DST at 00:00 on 1 April 2012, so in Havana the QML engine resolves it back onto 31 March and the heading reads March 2012 above an April grid. Noon exists on every first of the month there has been, and keeping it local is what makes Qt.formatDate print the right month in every zone.

The Apia case comes from #13186: Samoa skipped 30 December 2011 entirely, so unlike a one-hour midnight gap, Node's V8 cannot resolve its way past it, and the test fails on the old grid under Node instead of only in the shell.

Co-Authored-By: Codex Medium <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:14:23 +02:00
David Heinemeier HanssonandClaude Opus 5.5 0b9dace018 Unify input methods with installer keyboard selection (#14433)
* Unify input methods with the installer keyboard selection

* Preserve inherited input keys and later keyboard changes

* Exercise input engines across desktop toolkits

* Show input modes in the keyboard indicator

* Keep Pinyin setup prompts visible

* Remember input selections before an app gains focus

* Remove the Japanese input welcome notification

* Show explicit choices on notification cards

* Suppress the Cloudpinyin first-use prompt

* Show the input switching shortcut in keyboard hints

* Verify input setup and composition stay quiet

* Use Super+I for input switching independently of Fcitx keys

* Restore linked input settings and attribute test notifications to Fcitx

* Correct the input setup shortcut hint

* Keep input tooltips to the name and switching shortcut

* Place the input language selector with the right bar widgets

* Show input switching only for multiple layouts or methods

* Unify input method and keyboard layout switching

* Select keyboard layouts and input methods in the Typing menu

* Apply typing selections immediately from the menu

* Use installer keyboard choices and keep typing selections accurate

* Queue typing changes and wait for saved selections

* Keep queued typing selections when reopening the menu

* Cycle keyboard layouts alongside composition engines

* Avoid shadowing Python typing and retain pending mixed input selections

* Keep input clicks working through controller and compositor failures

* Move the keyboard layout widget to the right side of existing bars

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

* Map keyboard choices to the layouts the installer sets

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

* Keep the switching order when changing input methods

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

* Set input method fonts only after the selection saves

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

* Restore confirmed picker checks when an immediate change fails

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

* Route Super+I through the shell's input reader so unfocused choices stick

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

* Restore the input group when the font preference cannot be written

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

* Drop the azerty conversion now that the installer no longer offers it

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

* Apply the owner's keyboard layout once before persisting the input choice

A quattro merge kept a second apply_keyboard after the retry loop, which
re-ran loadkeys and firstboot, ignored their failure, and overwrote the
XKB layout setup supplies for keymaps kbd-model-map lacks.

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

* Give the Lao keyboard choice a Lao desktop layout

The row used la-latin1, kbd's Latin American Spanish keymap, so Lao
installs typed Spanish. There is no Lao console keymap; like Korean, the
console stays US and the desktop gets la, which Omarchy keeps behind US
as a non-Latin layout.

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

* Load engines installed after Fcitx started when they are selected

The Typing menu refused an engine whose package an update had installed,
since Fcitx discovers engines only at startup. Restart input once and wait
for the engine; name the package when it really is not installed.

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

* Cycle input methods back with Super+Shift+I

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

* Drop notification action buttons now that the Pinyin prompt is suppressed

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

* Cycle input only through the shell's reader

Without the shell there is no bar or menu either, so the one-shot cycling
path only duplicated the reader's logic.

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

* Add engines from the command line through the Typing menu's path

omarchy setup input <engine> had its own restart, append, select and
live-rollback code in configure.py, parallel to the menu's. It now
installs the packages and appends through typing_setup, leaving
configure.py with install-time seeding and update defaults.

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

* Keep Ctrl+Space out of Fcitx's toggles instead of probing for it

The terminal prefix bind read the Fcitx config and profile on every press
to avoid fighting a Ctrl+Space toggle. configure --defaults now removes
that key from every trigger list, so the bind only checks for a terminal.

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

* Run the input-method scripts with the system Python

A bare python resolves through PATH, so a mise, pyenv or venv interpreter
could run them without the system's PyGObject.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:16:30 +09:00
outfoxxed 51717f10ba Patch all QtQuick imports to 6.11 to work around plugin Color bug 2026-10-10 03:08:55 -07:00
+12 e1b0e5e9bb Converge omarchy-mac and omarchy-mx-mac into upstream Omarchy (#14431)
* Add omarchy-hw-platform to tell Apple Silicon and Qualcomm machines apart

aarch64 covers Apple Silicon Macs, Snapdragon laptops, Raspberry Pis and
VMs, so the CPU architecture can't pick platform setup. omarchy-hw-platform
prints apple-silicon, qualcomm, generic-aarch64 or generic from the vendor
prefixes in the device tree's root compatible (/proc/device-tree, then
/sys/firmware/devicetree/base) and the CPU, and fails when they contradict
each other. Its qualcomm answer uses the same evidence as the dragon
branch's omarchy-hw-qualcomm-soc, and the fixtures check the two agree.
omarchy-hw-apple-silicon is the Apple predicate on top of it.

An image built away from its hardware names its target in a root-owned
manifest, /var/lib/omarchy/image/target (format=1, platform=). While the
root is being built rather than booted, the detector answers from it and
never reads the build host's device tree; a booted system always answers
from its hardware. As root it restarts in an empty environment, so no
variable picks the platform for privileged setup.

Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>

* Reject platform gates keyed only on the CPU architecture

An architecture check is right for an ABI, a binary or a repository's
$arch, not for picking a platform. The lint lists each shipped check after
review and fails on a new one, so platform setup goes through
omarchy-hw-platform.

* Compose default packages from base, architecture and platform lists

omarchy-reinstall-pkgs installed the base list on every machine. A
platform's default set is now the base list, then install/omarchy-aarch64.packages
on aarch64, then the platform's own list when it has one
(install/omarchy-qualcomm.packages adds linux-firmware-qcom).
omarchy-pkg-defaults prints the set for the detected platform, or for the
one named, and reinstall installs it. x86_64 still gets exactly the base
list, and an image build gets its target's set.

* Early-load thunderbolt only where the kernel builds the module

linux-aarch64 has no thunderbolt module, and mkinitcpio fails the whole
image over a MODULES entry it cannot find. The optional form, thunderbolt?,
is not enough: a systemd initramfs copies MODULES as written into
modules-load.d, where "thunderbolt?" names no module, and a kernel without
the module would still list it there. thunderbolt_module.conf now adds plain
thunderbolt only when modinfo finds it as a module for the kernel being
built.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Compose mkinitcpio HOOKS from an early baseline drop-in

omarchy_hooks.conf assigned HOOKS outright, and mkinitcpio sources drop-ins in
name order, so it ran after any numbered drop-in (90-*.conf) and threw its
hooks away. HOOKS now comes from 00-omarchy-hooks.conf, which sorts first, and
later drop-ins add to it. The NVIDIA kms filter and the vconsole handling stay
in omarchy_hooks.conf. With the drop-ins Omarchy writes, machines keep the
same HOOKS, MODULES and FILES; a user drop-in that sets HOOKS and sorts before
omarchy_hooks.conf now takes effect instead of being overwritten.

Migration 1786605598 now sources the baseline first, since without it the
migration would read empty HOOKS and also rebuild hybrid systems. It skips a
machine that lacks the baseline.

* Resolve the LUKS parent from raw lsblk output

lsblk -s prints the ancestor tree with box-drawing prefixes ("└─/dev/nvme0n1p6"), so the crypto_LUKS parent came back as a path that does not exist and first-boot setup stopped with "Could not locate the LUKS device to re-key". -r prints raw names. Factory reset uses the same lookup.

* Check the factory-reset passphrase against keyslots only

cryptsetup open tries enrolled tokens before the key file, so a TPM2 or
FIDO2 token could authorize the re-key with a wrong passphrase.

* Test LUKS parent lookup during owner setup and factory reset

Exercise raw ancestry output without invoking provisioning or reset. Cover btrfs subvolume roots, absent encryption, failed probes, and explicit cryptdevice specifications.

* Cover LUKS parent lookup through an LVM volume group

* Journal the first-boot LUKS re-key so an interrupted attempt resumes

A first boot killed after the staged install key's slot was retired left the
key file behind, and every retry then stopped at "the staged LUKS key no
longer unlocks". A lost key file with the boot-time auto-unlock still
configured let setup finish with the disk unlocking unattended.

The re-key now runs from install/provisioning/luks-rekey.sh and records its
phase and slot numbers (never key material) in
/var/lib/omarchy/provisioning/luks-rekey.state. It reuses the owner's slot
instead of adding a duplicate, retires LUKS1 slots too, and destroys the
staged key only after the volume proves it opens nothing. Setup refuses to
finish while any part of the provisioning unlock remains. The journal ends at
phase=done and leaves together with pending, so a retry after the staged key
is retired must use the password the disk already holds.

Key checks exclude LUKS2 tokens: cryptsetup open tries an enrolled token
(say a TPM2 slot a previous owner left behind) before the key and reports its
slot whatever key it was given. Slot listings read only the keyslot section,
since a luks2-keyring token looks like a keyslot in luksDump.

* Change the root volume's LUKS key before the login and root passwords

Installation gives the login user, root and the disk holding / one password,
but omarchy-drive-password changed only the LUKS key, so the three drifted
apart. For the disk holding / it now checks the current password opens the
disk, changes the LUKS key, confirms the new key opens the disk and the old
one no longer does, and only then sets the login and root passwords. Any other
encrypted drive changes its LUKS key alone. Every drive now asks for the
current password with gum instead of cryptsetup's prompt.

A journal in ~/.local/state/omarchy/drive-password.state (disk UUID, key slots
and phase, never a password, written with the re-key journal helpers) lets the
next run finish an interrupted change. The rerun accepts only the old key
(removing the slot the interrupted change added) or the new one (retiring the
old slot), so another key such as a recovery passphrase never becomes the login
password. luksChangeKey is judged by whether the new key opens the disk, not
by its exit status, and key checks exclude LUKS2 tokens, which cryptsetup
open would otherwise accept in place of any password.

Runs are serialised, cryptsetup calls hold a disk lock, a / whose LUKS
partition cannot be resolved fails closed, and passwords are never traced. A
new password equal to the current one is refused, and --pbkdf argon2id is
passed only to LUKS2, so LUKS1 drives can change their password again.

* Add omarchy-lifecycle-dispatch for platform boot operations

Provisioning, factory reset and update call a fixed set of operations
through it. A platform whose boot chain the generic Limine path can't drive
registers a directory of root-owned entrypoints in the dispatcher; every
other platform gets a no-op and keeps its generic path. Apple Silicon is
registered to omarchy-mac-boot's /usr/lib/omarchy/mac-boot, with its
required operations failing clearly when the package or an entrypoint is
missing. docs/lifecycle-dispatch.md is the contract, including how a
Qualcomm boot package would plug in.

* Let the platform's boot package own first-boot setup's boot chain and key slots

Owner provisioning asks provision-prepare before the owner form, hands the
staged unlock to provision-commit and provision-verify where the platform
implements both, and records the kept LUKS slot through luks-slots before
the staged key is destroyed; the disk password change records the owner's
new slot the same way. On x86, Snapdragon and generic aarch64 every call is
a no-op and first boot is unchanged.

* Reset through the platform's boot package where it owns the boot chain

Where a platform implements the reset operations, reset-prepare and
reset-verify stage and prove the factory root's boot files, the throwaway
slot is added only after that, and reset-commit gets the key on standard
input once the factory root is active. A failure before the switch rolls the
boot state back and revokes the slot. Elsewhere the Limine UKI path is
unchanged.

* Check the platform's boot chain after an update

omarchy-update-boot runs update-verify after AUR packages, the last
sudo-capable step, so it also covers the initramfs rebuilds they trigger.
It follows third-party build code, so like AUR it authenticates through the
no-update wrapper. A failed verification finishes the update's remaining
steps, then exits non-zero without offering the reboot. Where the platform
does not implement it nothing runs and nothing asks for root.

* Hand a machine to its platform's migration through lifecycle dispatch

migrations/1790347292.sh resolves the new optional migrate operation and,
where the platform's boot package implements it, runs it as root. The
entrypoint moves the machine onto the platform's official package set,
idempotently and resumably; its failure leaves the migration pending. Once
it succeeds, a root-owned marker spares other accounts the migration and its
root prompt. Everywhere else, Macs without the boot package included, the
migration is a no-op, and an undetermined platform keeps it pending.

* Refuse packages tagged for another platform in a pacman hook

A platform package names its platform with groups=('omarchy-platform-<name>'),
<name> being what omarchy-hw-platform prints. omarchy-settings ships a
pre-transaction hook, the guard it runs and a copy of the detector, so the
guard is resident before the runtime and any platform package. The guard
reads tags from the synced databases and refuses a transaction that installs
a package tagged for another platform; a transaction without a tagged
package passes without asking which machine this is. Image builds answer
from the image's root-owned target manifest, as the detector does.

The guard ships on aarch64 only: no platform package is built for x86_64,
so x86 transactions run no hook. Hardware setup starts by proving the guard
is resident and checking the packages an installer placed without it. On
generic aarch64, where no platform packages exist, a missing guard is
logged, not fatal, and on x86 it is expected.
docs/platform-guard.md is the contract.

Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>

* Enable Voxtype's Vulkan backend only when it is installed

omarchy-hw-vulkan reports a Vulkan runtime, not whether this voxtype build
has a Vulkan backend. The aarch64 voxtype-bin package ships only the CPU and
ONNX binaries, so on any aarch64 machine with a Vulkan driver setup ran
`voxtype setup gpu --enable` and printed "Vulkan backend not installed" in
the middle of an otherwise working install. Require the backend binary too.

x86_64 voxtype-bin always ships /usr/lib/voxtype/voxtype-vulkan, so x86
installs still switch to the GPU backend as before.

Ported from omacom/omarchy-mac#483.

Co-authored-by: Oliver Lukschander <33756270+oliverlukschander@users.noreply.github.com>

* Link the system Widevine CDM into Brave

The widevine package registers its CDM for Chromium and Firefox only.
Brave looks for the CDM in its own install directory, finds nothing, and
DRM sites (Netflix, Apple TV+, Spotify) fail with a blank player and no
useful error. This hits machines where Widevine comes from that package,
such as aarch64 ones, since no Widevine component is published for them.

Link /opt/WidevineCdm/chromium into /opt/brave-bin and /opt/brave-origin-bin
through as_root when Brave or Brave Origin is installed, and add a migration
that repairs existing installs. Both do nothing when the widevine package is
absent or the browser already has a CDM. A standard x86 install has no
widevine package, so nothing changes there.

Ported from omacom/omarchy-mac#474, extended to Brave Origin.

Co-authored-by: Caleb Marshall <68087341+cajomar@users.noreply.github.com>

* Report failed setup steps on the console

With file logging, run_logged sends a failing leaf's output and its
"Failed:" marker to the install log only, so the caller sees setup stop
with an exit status and nothing else. A missing command (exit 127) was the
case that made this hard to diagnose.

Print the failed script, its exit status and the log path on stderr. Stdout
logging mode already shows the failure marker and is unchanged, and
successful steps stay quiet.

Ported from omacom/omarchy-mac#389 (the logging part).

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Let a migration defer itself without stopping the update

A migration that exits 75 changed nothing and cannot finish yet: omarchy-migrate
leaves it pending with its login notification, runs the migrations after it and
exits 0, so omarchy update carries on. Any other failure still stops the queue.
The platform migration passes a deferred refusal of its entrypoint through, and
defers itself where the dispatcher can't resolve its entrypoint (the platform
can't be told, or the entrypoint isn't trusted): nothing has run then, so the
update goes on to its boot verification, which fails there, as documented.

* Keep Apple Silicon's systemd initramfs on the HOOKS baseline

00-omarchy-hooks.conf set the busybox line on every machine. On a Mac,
omarchy-mac-boot's drop-ins then add the Asahi firmware hooks, but its
encryption drop-in reads busybox encrypt as a Mac unlocked through
cryptdevice= and leaves the line alone. The image came out as
"... block encrypt asahi omarchy-vendorfw filesystems ...", with no
systemd, sd-encrypt or omarchy-mac-encrypt, so an encrypted Mac could not
unlock its disk. Reproduced against an M1 Pro's installed drop-ins.

The baseline now asks omarchy-hw-platform. Apple Silicon starts from the
systemd line its boot package builds on. A Mac whose own mkinitcpio.conf
carries the asahi hook, or busybox encrypt, keeps that line, as a legacy
install needs. x86, Snapdragon and generic aarch64 keep today's busybox
line, as does a machine with no detector. The detector is the runtime's,
or else the copy omarchy-settings ships for its platform guard, so a
settings package newer than the runtime still places a Mac. A detector
that cannot place the machine stops the build.

The HOOKS test now composes the baseline on each platform and on legacy
Mac lines. A new test composes it with omarchy-mac-boot 20260926-1.45's
drop-ins, kept as fixtures, and checks the exact HOOKS the M1 Pro builds.
The NVIDIA test pins its platform.

* Hide optional installs that have no package on this machine

Not every optional install exists on every architecture: some vendors ship no
aarch64 build, and some repositories don't carry a package at all. Each
Install row now carries a `when:` guard naming every package its install
command adds, checked with omarchy-pkg-available, so a row with nothing to
install disappears instead of failing halfway through a pacman transaction.
The rows that build from the AUR (Chrome, Edge, Brave, Brave Origin, Zen,
NordVPN) are guarded on the CPU architectures their vendors publish for.

omarchy-pkg-available asks the sync databases: one `pacman -Slq` per batch,
and `pacman -Sp` for a name that list doesn't hold, so a provided name
(libappindicator-gtk3) and a version constraint resolve the way `pacman -S`
will. The menu's guard prelude sources the same function, so the whole batch
still costs one fork. omarchy-reinstall-pkgs reports and skips a default
that no configured repository offers instead of failing the transaction.

On x86_64 every guarded row resolves against the stable and edge
repositories, so the menu is unchanged there.

Tests derive each row's packages from its install command on x86_64 and
aarch64 and fail when the guard names different ones.

Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>

* Run 1Password with software GL where there is no render GPU

Electron draws a blank window when the machine has no DRM render node: a VM,
a headless or passthrough GPU, or a GPU whose driver isn't up yet (Apple M3
and later on Linux today). omarchy-hw-render-gpu tells whether a renderD node
exists, and omarchy-cmd-electron-gl-args prints --ozone-platform=wayland
--disable-gpu only when it doesn't.

omarchy-cmd-electron-gl-wrap installs a marked PATH wrapper in /usr/local/bin
that execs the real binary with those flags, so it follows the machine rather
than the moment it was installed. It refuses to replace a launcher it didn't
write, or to wrap a binary over itself, and stages the write so a failure
leaves the old launcher in place. omarchy-cmd-desktop-exec-repair points the
user's desktop entry at the wrapper by replacing only the executable token,
keeping arguments, other actions and customizations, with a backup.

The 1Password installer uses them when the machine has no render node. With
one, which is every x86 machine with a working GPU, nothing changes.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Record with wf-recorder when gpu-screen-recorder can't start

gpu-screen-recorder knows NVIDIA, AMD, Intel and Broadcom GPUs and aborts
during GL init on anything else, such as Apple Silicon's, before a recording
starts. Screen recording now falls back to wf-recorder, when it is installed,
if gpu-screen-recorder is missing or exits before the file appears. The
picker, file naming, notifications and post-processing are shared. wf-recorder
records one audio device, so desktop plus microphone go through a temporary
null sink that stop unloads, and its track is pinned to 48 kHz. Portal
capture and --resolution stay gpu-screen-recorder only.

The finalize pass keeps 48 kHz too: loudnorm declares 192 kHz output, so
without -ar the AAC encoder wrote 96 kHz from a 48 kHz recording, which many
editors decode as silence, and which on Apple Silicon makes the speaker
safety daemon lock the amps. #11092 makes the same one-line change; whichever
lands second drops it.

Stop and status matched `pgrep -f ^gpu-screen-recorder`, which misses
wf-recorder and matches any command line that starts with that path. The
script now records the recorder's pid, and the new
omarchy-capture-screenrecording-process checks or signals it by its
executable, through a pidfd opened before the check so a reused pid can't be
signalled. When that pid is no longer a recorder (a recording started before
this change, or one that died), every recorder the user runs counts, as it
does for the bar indicator and the Stop row, which use the same helper.

Where gpu-screen-recorder works, which is every x86 GPU it supports today, it
records as before.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Issac Trotts <15530+ijt@users.noreply.github.com>

* Hold logind's lid switch while a display it doesn't count is in use

systemd-logind treats a closed lid as docked only when an external display
is on a connector type it lists (HDMI, DisplayPort, DVI, VGA...). Displays on
"USB" connectors, which Apple Silicon uses for USB-C displays, aren't on that
list, and a missing comma fuses DVI-A and Composite into one entry. With such
a display in use, closing the lid suspended a laptop that was still being
used on it.

omarchy-system-lid-inhibit holds a handle-lid-switch inhibitor while one of
those connectors is connected and enabled in Hyprland, on a laptop where
HandleLidSwitchDocked is ignore, which is exactly the case logind would have
ignored had it counted the display. It releases the inhibitor when the display
goes. A compositor or logind that doesn't answer changes nothing, since
releasing then would suspend a closed lid. The inhibitor runs as a transient
user unit bound to the monitor watcher's unit, so a watcher that dies can't
leave the lid inhibited until logout.

The monitor watcher runs it alongside the clamshell sync. On a machine whose
external displays are all on connectors logind counts, it does nothing.

* Keep the focused workspace in front when a monitor goes away

When a monitor is unplugged, or the lid turns off the laptop panel, Hyprland
first warps focus to whichever monitor it lists first and only then moves the
removed monitor's workspaces over. The workspace you were on lands hidden
behind that monitor's own (Hyprland 0.56.2, CMonitor::onDisconnect).

monitor.focused fires before Hyprland records the new focus, so the monitor
still reported as active is the one being left: its workspace is remembered
until the event loop next turns, and a monitor.removed in that window focuses
it again once the removal has finished. An open special workspace already
comes along, and any focus or workspace change made before the restore runs
wins.

* Drive the keyboard backlight from the ambient light sensor

Laptops with an ambient light sensor and a keyboard backlight LED now light
the keys in the dark and dim them as the room brightens, inverse to display
auto-brightness. omarchy-brightness-keyboard-auto polls the sensor every five
seconds (preferring an IIO device that names itself ALS, and converting a raw
channel with its offset and scale) and maps lux onto the LED, with a deadband
so it doesn't chase noise. It pauses while the screen is locked or the lid is
closed.

A level set with the keyboard-brightness keys wins until the light changes
enough that it no longer fits. omarchy-brightness-keyboard records each level
it sets in the session's runtime directory (a private state directory without
one), so the loop can tell keys turned off on purpose from keys left off by
the lock screen or a restored zero, which it lights again.

The user unit runs it in the graphical session. Its ExecCondition asks
--available, so it is a no-op on machines without both a sensor and a
keyboard LED. First run enables it, and a migration enables it for existing
installs, writing the enable symlink itself when there is no live user
manager.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Gabe Ohlson <3355909+6abe@users.noreply.github.com>

* Defer an image build's hardware setup to the machine's first boot

An image is built away from the machine it will run on, so its hardware
leaves would probe the builder. While the image manifest the detector
already reads (/var/lib/omarchy/image/target) exists, omarchy-apply-hardware
queues every leaf of install/hardware/all.sh instead of running it and arms
omarchy-provision-hardware.service. On the first boot, before owner setup
and the login screen, omarchy-provision-hardware retires the manifest and
runs the queue on the real hardware: a failed step stays queued for the
next boot (exit 75), and the initramfs is rebuilt once after the last step
when a step changed its inputs or asked for it. Bluetooth starts its
service on that boot, whose target is already up. Root ignores any
environment override of these paths. Without a manifest, as on every
ISO install, hardware setup runs exactly as before. Nothing here fetches
package databases: an image carries its sync databases and every package
its steps install.

* Give an image-built root a UTF-8 locale when LANG is still C

A root built from a distribution tarball instead of the ISO (Arch Linux
ARM's ships LANG=C) never went through the ISO's locale step, so it ran
non-UTF-8. The config phase now selects en_US.UTF-8, generating it if
needed, but only when LANG is unset, C or POSIX: any named locale is
somebody's choice and stays. The ISO writes the locale before this runs,
so an ISO install is unchanged.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Let a platform's packages run their own boot, system and user setup

Three dispatch operations give a platform one place for the setup it
needs beyond Omarchy's, instead of inline platform branches in the setup
leaves. The last hardware leaf runs setup-boot, then setup-system, as
root, so an image build queues them with the others and its first boot
runs them, with image-first-boot (offline-safe, asking for the one boot
rebuild instead of building). setup-boot belongs to the boot package and
is required on Apple Silicon, where hardware setup has no other way to
run the Mac's boot setup; a failed setup-boot stops the leaf before
setup-system. setup-user runs as the user being set up, refuses root,
and gets only the user's home and session variables: from the last user
leaf at finalization (skipped inside an image build, where the detector
would read the build host) and again from first run once the session is
up. On Apple Silicon setup-boot resolves in omarchy-mac-boot's directory
and the other two, both optional, in omarchy-mac's, /usr/lib/omarchy/mac;
x86, generic aarch64 and Qualcomm register none, so all three are no-ops
there.

A failed finalization now keeps first run pending, like any failed
first-run step, instead of letting the other steps mark it complete.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Find batteries, lids and panels that aren't named like a PC's

Laptop detection assumed PC names: a battery called BAT*, a lid in /proc/acpi, a
touchpad or trackpad in the device name, a GPU backlight. Apple Silicon has
none of them (macsmc-battery, the SMC lid switch, apple-mtp-multi-touch,
apple-panel-bl), and some Framework and Chromebook firmware calls its battery
CMB0, so those machines lost the battery panel, laptop-only services, lid
handling, the touchpad toggle and display brightness.

- Battery presence goes by type instead of name, and skips a battery scoped
  Device (a wireless mouse or keyboard), so a desktop with one isn't a laptop.
- Battery status takes the first UPower battery that powers the machine, reads
  thresholds and cycles from it (BAT* stays the fallback), shows the magnitude
  of a signed power_now, and rounds the percentage half-up for display like the
  bar, while the charge-hold check keeps the unrounded value.
- A laptop is also one with any input device that reports a lid switch
  (SW_LID), whatever its name: Apple's SMC input device has the same name on a
  Mac mini, where it reports none. Whether the lid is closed comes from ACPI
  where there is an ACPI lid, as before; otherwise logind is asked, with a
  short timeout, only when a device reports a lid switch.
- A device named touchpad or trackpad still wins; only when there is none does
  the touchpad match take a mouse udev marks as a touchpad (Apple's
  multi-touch trackpad), never a touchscreen.
- The platform package can describe its displays in
  /usr/share/omarchy-platform/displays.conf: backlights that never drive the
  panel (a Touch Bar's), the panel's own where there is no GPU backlight, and
  a display driver without DDC channels, where a probe walks unrelated I2C
  buses for about 3 s: there only a connector with a ddc node is probed, and
  a monitor without one reports no brightness instead of dimming the built-in
  panel. Omarchy ships no such file and names no device in it; without it the
  backlight choice and DDC probing are unchanged.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
Co-authored-by: Sparsh Kumar <22640187+skkumarsparsh@users.noreply.github.com>
Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>

* Keep the user's XDG config and state directories in platform user setup

setup-user runs with an allowlisted environment, which dropped
XDG_CONFIG_HOME and XDG_STATE_HOME. A platform's user setup reads the user's
configuration and keeps its setup markers there, so for a user who sets
either it looked in, and wrote to, the default directories instead. Both now
pass through with the rest of the session; XDG_DATA_HOME and everything else
still don't.

* Let a platform package add Hyprland defaults of its own

A platform's runtime package may need Hyprland defaults of its own (a Mac's
capture keys and three-finger workspace swipe, say), and Omarchy's config
should carry no branch for them. Three directories under the platform root,
/usr/share/omarchy-platform, now take them. The one installed platform package
owns that root, Omarchy reads it by literal path and ships nothing there, so
without a platform package nothing changes. Each file is loaded by its path,
so it can't shadow a module, and one that fails is logged and shown as a
notification while the rest of the config loads.

- hypr/defaults/*.lua loads before Omarchy's defaults. A
  chord bound there replaces Omarchy's default for it, and a decorator it adds
  runs before every later bind, so it can bind what has to run first. It sees
  the command a bind runs, including the command a shell shortcut such as
  { menu = "root" } stands for when it binds the shell's global shortcut.
- hypr/settings/*.lua loads after Omarchy's defaults and
  before the theme, for settings that replace Omarchy's.
- hypr/gestures/*.lua loads after the user's files, only for what must
  see them: the gesture registry, which now records on every platform, lets a
  default gesture step aside for the user's own.

The user's files load after the first two and can unbind or rebind any of it.

* Let a platform package name keys in the keybindings menu

The keybindings menu shows a key by its keysym, which on some keyboards is not
what the key says: on a MacBook, XF86MonBrightnessUp and Down are the F2 and
F1 keys, so the menu listed rows like "SHIFT + XF86MonBrightnessUp". A
platform package can now list names for such keys in /usr/share/omarchy-
platform/key-names, one "<keysym> <name>" per line, and the menu shows those
instead. The file is part of the menu cache's key, so installing or changing
it refreshes the menu. Without it the menu is as before.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: kx0101 <90087463+kx0101@users.noreply.github.com>

* Keep the top bar out of a panel's camera cutout

A laptop panel with a camera notch at its top (Apple Silicon MacBooks, for
one) covered the middle of the top bar, and a bar shorter than the notch left
a sliver of every window beside the camera. Omarchy knows no panel's cutout
itself: the platform's own package describes them in
/usr/share/omarchy-platform/display-cutouts.json, by
connector, physical mode and the rows the cutout covers. On a described panel
a top bar is never shorter than the cutout, its center section sits beside
the right one, and hiding it parks all of it; [bar] notch-height sets that
floor by hand, on that panel only. Without a description, which is every
machine without such a package, the bar is as before.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Keep PC and Intel Mac quirks off Apple Silicon

A few hardware steps and commands written for PCs and Intel Macs do the wrong
thing on Apple Silicon:

- The Intel Mac Wi-Fi quirk turns off the Broadcom firmware supplicant for
  every Mac whose DMI vendor is Apple and whose chip is on its list. Apple
  Silicon reports Apple Inc. through U-Boot's DMI tables, and the M1 Pro and
  Max's BCM4387 is on the list, but there the firmware supplicant is the one
  that works: turning it off makes firmware commands time out and breaks
  scanning. The install step and its migration now skip Apple Silicon.
- fnmode=2 for Apple-like keyboards is left to the Mac's own platform package
  on Apple Silicon, where the built-in keyboard's top row depends on it.
- The SPI keyboard check reads the DMI product name without failing the
  hardware pass where there is none.
- The Windows VM, an x86_64 guest under KVM, runs on x86_64 only, and direct
  boot (U-Boot finds the bootloader and makes its boot entries itself)
  refuses Apple Silicon; the menu hides both there. Hibernation setup skips
  it; its kernel has no hibernation today, and resume there is not wired.

Everywhere else each step runs as before.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Let a platform's runtime package adjust the apps it needs to

Some apps need a platform's own touch once they are installed. On Apple
Silicon, Chromium-family browsers must decode video in software (the hardware
decoder renders green frames in them), and Steam only runs through a
FEX launcher, which then depends on Steam and must go before Steam can be
removed. Until now the Mac runtime did this with inline branches in the
installers.

Two optional dispatch operations take their place. post-install <app> runs
as the user once an installer has installed the app and finished its own
setup of it: every browser install (after its flags file, before it says the
browser is installed) and Steam's (after the package, before the 32-bit
drivers and the first launch). pre-remove <app> runs before Steam's removal
drops its packages. Where a platform registers them they refuse root (a
platform with nothing registered is a no-op, so sudo installs still work
there) and get the same home and session variables as setup-user, and a
failure fails the install or stops the removal. On Apple Silicon they
resolve in omarchy-mac's directory; every other platform registers none, so
installs and removal run as before there.

Steam's 32-bit driver step also stops failing the install on a machine with no
Intel, AMD or NVIDIA GPU (Apple Silicon, but also Snapdragon or a VM): it has
nothing to add there and now says so.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Ask the platform's boot package before an update takes over its files

When an Omarchy package's upgrade stops only on files no package owns, the
update moves them aside and retries. On a Mac some of those files belong to its
boot chain or were written by its image (initramfs and pacman configuration,
say), and moving them aside can leave it unable to boot or update. The Mac
runtime refused those paths with an inline list.

update-takeover <path>... asks the platform's boot package instead, before
anything moves: read-only, exit 0 lets the takeover go ahead, anything else
keeps every file and the update fails as it did before the retry existed. The
update resolves it as the user and runs it with sudo only where it resolves,
inside the authorization the move needs anyway. On Apple Silicon it is
required: a Mac without its boot package, or with one too old to answer, keeps
its files. Every other platform registers none, so the takeover goes ahead as
before, asking no one.

* Install the Mac's package set on Apple Silicon

omarchy-pkg-defaults already composes install/omarchy-<platform>.packages
after the base and aarch64 lists, but Apple Silicon had no list, so a Mac got
the generic aarch64 set: no omarchy-mac or omarchy-mac-boot, whose entrypoints
the dispatcher requires there.

install/omarchy-apple-silicon.packages names the Mac's runtime and boot
packages, plus the defaults an owner may remove: the startup-volume tool,
hardware video decode, Widevine, and wf-recorder, since gpu-screen-recorder
can't capture on Apple Silicon and screen recording falls back to it. What a
Mac can't work without (its speaker stack, PipeWire's PulseAudio server and
realtime scheduling, the GPU's Vulkan driver) comes as omarchy-mac's
dependencies, so a new Mac driver needs no Omarchy change. Other platforms
compose exactly what they did.

Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>

* Give existing aarch64 machines the zram swap Omarchy configures

omarchy-settings configures swap on zram and tunes memory reclaim for it.
x86_64 gets zram-generator from its installer and new aarch64 installs from
their default packages, but an aarch64 machine installed before
omarchy-aarch64.packages named it has no generator, so the configuration asks
for a device nothing creates.

Migration 1790328426 installs zram-generator where the platform's default
packages name it and it is missing, and starts the zram swap wherever it is
down, unless no device is configured (then the generator makes no swap unit)
or the administrator masked it. A swap that won't start, or whose unit can't
be read, only warns: the generator's unit starts with every boot, so a reboot
retries it, and failing the migration would stop every update. A failed
install waits (75) for the next migrate, as it does where the platform can't
be told, changing nothing. It keeps the name it has in omarchy-mac, so Macs
that already ran it skip it. It also retires an /etc copy of the drop-in
identical to the packaged one, which earlier Mac setups made and which would
hide the packaged file's updates; an edited copy stays. On x86_64 the default
packages don't name the generator and there is no copy, so it does nothing.

* Keep an ARM machine's repositories when it changes channel

omarchy-refresh-pacman and install finalization copied x86_64 templates
everywhere, so on an aarch64 machine a channel change or a finished install
wrote Arch Linux's x86 [core]/[extra]/[multilib] and dropped Arch Linux ARM
and the platform's own repositories.

Channel changes now tell the platform apart. x86_64 is unchanged. On aarch64
the machine's configuration is kept, with its repositories, mirrors and
signature policies, and only the [omarchy] server moves to the new channel;
a configuration whose Omarchy server can't be identified is refused
unchanged, and one with no Omarchy repository gets it at the end (not on
Apple Silicon, whose own repository must not come first). Only pacman.conf is
backed up and rewritten, straight from this process through sudo, never
through a file another process could change first.

Before that, a channel must be qualified for the platform: a signed upgrade
and reboot tested there, not only a published repository. x86_64 keeps
stable, rc and edge; Snapdragon and other aarch64 machines take edge, as
dragon does; Apple Silicon takes none until the Mac packages qualify on edge.
The refresh then checks, as the user, that the channel's omarchy.db
publishes the runtime pair, and on Apple Silicon omarchy-mac and
omarchy-mac-boot. omarchy-channel-set refuses an unqualified channel before
anything else, the dev confirmation included.

Install finalization writes a whole template, as before on x86_64. Other
aarch64 platforms get Arch Linux ARM (dragon's pacman-aarch64.conf and
mirrorlist-aarch64, byte for byte) with its keyring, plus Omarchy on a
qualified channel. Apple Silicon gets omarchy-mac's template on a qualified
channel and keeps its image's configuration otherwise. In an image build,
finalization leaves the keyring to the machine's first boot, so machines
flashed from one image don't share a pacman master key. On an unqualified
channel, a machine that gets no Omarchy repository is told why.

Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jim Martin <1334144+jdvmi00@users.noreply.github.com>

* Keep Arch Linux ARM's signing keys current on ARM machines

omarchy-update-keyring reinstalls archlinux-keyring before every system
upgrade, since keys change without a version bump. An aarch64 machine's Arch
Linux ARM repositories are signed with keys from archlinuxarm-keyring, which
nothing refreshed, so a rotated ALARM key failed the upgrade that followed.

On an aarch64 platform where archlinuxarm-keyring is installed, it is
reinstalled in the same transaction. On x86_64 the keyring step runs exactly
as before, even where that package happens to be installed.

* Mark the runtime's platform files as deciding at runtime

The omarchy-settings recipe leaves the mkinitcpio, Limine, memory and USB
drop-ins out of its aarch64 package, because they used to assume x86_64. That
leaves an aarch64 install without the HOOKS baseline its initramfs needs, and
without the zram and reclaim settings every other Omarchy machine gets.

Each of those files now decides at runtime whether it applies: the HOOKS
baseline asks the platform, thunderbolt is added only where the kernel has the
module, the Limine files are read only where Limine is installed, the oomd
drop-ins are inert without systemd-oomd, aarch64 installs the zram generator
the zram drop-in needs, and the USB autosuspend option is ignored by a kernel
that builds usbcore in. default/settings-runtime-profile says so, file by
file; a recipe that finds it ships the same files on every architecture
(omacom/omarchy-pkgs#638), and older sources keep today's aarch64 package. A
test checks that every file it names exists and that each runtime decision is
in place.

* Set the wireless regulatory domain from the owner's timezone at first-boot setup

A deferred install (an Apple Silicon image, an OEM ISO install) runs its
hardware steps before the owner picks a timezone, on the UTC placeholder,
which names no country, so install/hardware/set-wireless-regdom.sh leaves the
regulatory domain unset and the radios stay on the world domain. Owner setup
now runs that step again once the owner's timezone is set, and applies it to
the radios through wireless-regdb's helper, since no reboot follows setup. An
up-front install, which picks the timezone before its hardware steps, is
unchanged.

* Ask again for a keyboard layout first-boot setup could not apply

First-boot setup kept going when the chosen layout did not take: loadkeys
errors were ignored, a keymap localectl did not list kept the default, and a
failed or partial write of /etc/vconsole.conf was only logged. The owner then
typed the disk password in one layout while the boot files asked for it in
another, and could only unlock the disk by typing US-style.

The keyboard step now fails closed. On a console loadkeys must succeed, the
keymap must be in localectl's list exactly (a list localectl cannot produce
counts as a failure too, logged apart), and after systemd-firstboot or the
localectl fallback, /etc/vconsole.conf is read back in a clean shell and must
name the chosen keymap with an XKB layout. Any failure is logged, the owner is
told the layout could not be set, and the layout is asked for again, so the
password form is never reached with a layout that did not take.

* Refresh a platform's own signing keyrings before an update

omarchy-update-keyring reinstalls archlinux-keyring (and Arch Linux ARM's on
aarch64) before the system upgrade, so a rotated key never fails that
upgrade's signature checks. A platform's own repository has a keyring of its
own that nothing refreshed: on Apple Silicon, asahi-alarm-keyring for
[asahi-alarm].

A platform package now lists those keyrings in
/usr/share/omarchy-platform/keyrings, one package name per line, and
omarchy-update-keyring reinstalls each one that is installed in the same
transaction. It adds no repository the machine didn't already trust. A line
that isn't one keyring package name is skipped with a warning, so a mistake in
the list can't stop every update. Without the file, as on x86 today, the step
runs as before.

* Drive an untyped default input from the audio panel

Quickshell types only exact media classes, so a virtual source (an
Audio/Source/Virtual: EasyEffects' source, or the microphone mapping a
platform package adds) has no PwNode.audio. With one as the default input the
audio panel showed the input at 0% and its slider changed nothing, the peak
meter stayed flat, the microphone widget read it as muted, and the panel could
not list it unless it was already the default.

Such an input now goes through wpctl for its volume and mute (UntypedInput in
shell/Commons, one reader and one pactl subscribe for the whole shell), the
panel meters it with the new omarchy-audio-source-level, and the input list
takes an untyped node PulseAudio lists as a source.

A platform whose audio runs through its own processing can also name nodes
that are neither devices nor apps in /usr/share/omarchy-platform/audio.json,
which Omarchy ships none of: the panel and the microphone widget leave them
out, and a "replaced" node only while its replacement exists. Without the
file nothing is hidden.

On every machine: the input list leaves out a source whose ports are all
unavailable (an empty headset jack) unless it is the default, as the output
list does for sinks; choosing an input no longer moves a filter's own capture
(a source output with node.link-group), which would feed the filter from the
new input or from itself; the shell's own level meters no longer light the
microphone widget; and omarchy-audio-sink-availability reads pactl in the C
locale for sinks too, since "not available" is translated.

* Give first-boot setup an XKB layout for keymaps systemd cannot map

systemd-firstboot writes an XKB layout only for keymaps listed in
kbd-model-map. Nine the setup form offers are not (Colemak, Azerbaijani,
Bulgarian, Czech, Swiss German, Kyrgyz, Norwegian, Polish, Ukrainian), so it
wrote KEYMAP= alone, the readback refused it, and owner setup could never get
past the keyboard step for them.

Setup now adds the XKB settings itself when firstboot left none, matching the
console map so the shared password types the same on the desktop: kbd's cz is
QWERTY, bg-cp1251 phonetic and azerty French AZERTY, and non-Latin layouts keep
a US layout to switch to, as kbd-model-map's own rows do. A test checks every
layout the form offers ends with an XKB layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Leave keys a firmware key turned off dark

The ambient keyboard backlight treated any 0 the keyboard-brightness keys had
not recorded as an accident and relit it within five seconds. Firmware Fn keys
(Fn+Space on ThinkPads and Framework laptops, Fn+F10 on Dells) write the LED
directly, so on those laptops turning the keys off never stuck.

The lock screen's off now records a blank, and only that blank or a 1%
leftover is taken back; any other off is a choice that pauses auto like the
keys do. A wake clears the blank only once the restore succeeded, the command
runs one at a time so a blank racing a wake can't leave the LED and record
disagreeing, and the loop drops a blank once it has taken the keys back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Drop the optional-install availability guards

Each menu open checked every Install row against the sync databases, which
costs most of a second of pacman work on every machine to hide rows only an
ARM machine is missing. The fix for those is publishing the ARM packages, not
hiding the rows, so this removes omarchy-pkg-available, the Install rows'
availability and architecture guards, and reinstall's skipping of defaults no
repository offers.

The Windows VM row keeps its x86_64 guard, since no package fixes a guest that
needs an x86_64 host, and reinstall keeps its aarch64 channel handling.

This reverts commit aea001cbde4d0852b311ef42b5e8c380c719f833.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Put the current root back when the factory root cannot take its place

Factory reset moved the running root aside, marked the reset committed, then
renamed the staged factory root to @. If that second rename failed, cleanup
saw a committed reset: it skipped the boot package's rollback and kept the
throwaway LUKS slot, and since the swap had not happened it deleted the staged
root as well, leaving nothing at @.

The reset now commits only once the factory root is at @. A failed activation
renames the old root back first, then cleanup rolls the platform's boot state
back and revokes the throwaway slot. If that rename fails too, both roots are
kept, cleanup still rolls back and revokes, and the error names the rename
that recovers the machine. The generic Limine path has already rebuilt its
boot files from the factory root by then, as it had before this change.

* Leave other recorders alone when the saved recorder pid is stale

A saved pid that no longer named a recorder made status and stop select every
gpu-screen-recorder and wf-recorder the user ran, so stopping could interrupt,
and after five seconds kill, a recording started somewhere else.

A stale pid now means no recording of ours is running: the toggle starts a new
one, and --stop-recording finds nothing to stop. Stop acts on the recorder the
status check selected rather than selecting again. Without a saved pid, as for
a recording started before the pid was saved, every recorder the user runs
still counts.

* Name the audio a wf-recorder recording could not capture

With desktop and microphone audio both requested, wf-recorder records a null
sink fed by two loopbacks, and a loopback that failed to load was ignored: the
recording went ahead without that source, or silent when both failed. A
missing null sink or default device dropped audio just as quietly.

A source that cannot be captured is now left out with a notification naming
it, the loopbacks that did load are unloaded, and the recording carries the
source that remains, or no audio track at all.

* Show the Edge, Spotify and Dropbox installers on x86_64 only

These vendors currently publish Linux builds for x86_64 alone (the AUR's
microsoft-edge-stable-bin and spotify are arch=('x86_64'), and Dropbox lists
no Linux ARM client), so no native aarch64 build is available and choosing
one on an ARM machine only fails. Like the Windows VM row they keep a uname
check, which the menu answers once per open without asking pacman. The other
Install rows are unchanged.

* Revoke the generic reset's throwaway slot when the reset fails

The generic Limine path added its throwaway LUKS slot without recording it,
so a reset that failed before activation never revoked it. Once the factory
root's UKI was on the ESP, a failed activation that put the old root back left
a machine that booted the factory kernel and unlocked unattended with a key
readable on the ESP, while the screen said the current root was back.

The generic slot is now recorded like the platform's and revoked by cleanup
whenever the reset fails before the factory root is active. A failed
activation on that path says the boot files were already rebuilt from the
factory system and to run limine-update before rebooting.

* Clear a stale recorder pid when a recording starts

The recorder's pid is saved only once its file exists. Until then a pid left
by a recording that ended without a stop still counted as stale, so pressing
the toggle again while the new recorder started found nothing of ours and
could start a second recording. Starting a recording now clears that pid,
and during startup the toggle falls back to the user's recorders as before.

* Word the dropped-audio notice for every reason a source is left out

The notice said the dropped source "could not be captured", which was wrong
when the null sink itself failed to load: the microphone was never tried, and
desktop audio recorded alone as it always has. It now says the source could
not be added to the recording, true for a missing device, a failed loopback
and a failed null sink alike.

* Show the Heroic and Minecraft installers on x86_64 only

Heroic publishes Linux builds for x86_64 alone (its arm64 builds are for
macOS and Windows), and Mojang's Linux launcher is a single x86_64 tarball.
They join Edge, Spotify and Dropbox under the same uname check, so every row
whose vendor ships no native Linux aarch64 build is treated alike.

* Ask the recorder script whether its stop has a recording to end

The bar indicator and the menu's Stop Screenrecording row asked the process
helper whether any recorder the user runs was alive, while the toggle's stop
acts only on the recording it saved. With a stale saved pid alongside an
unrelated recorder, both offered a stop that did nothing.

omarchy-capture-screenrecording --status answers with the selection the stop
uses, and both now ask it. Status needs no recordings directory and never
notifies, so the recordings-directory check moved below argument parsing and
skips it.

* Copy every platform's pacman templates into place, as x86_64 does

On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.

Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Revert "Copy every platform's pacman templates into place, as x86_64 does"

This reverts commit 9a07e138cc6c3a19b58d2f3d7eb536dd3042ccc5.

* Copy every platform's pacman templates into place, as x86_64 does

On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.

Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Drop the migrations no installed machine needs

Fresh installs mark every migration done, and every Apple Silicon machine is a
fresh install, so the Mac handoff to omarchy-mac-boot (and the migrate
operation it called), zram for aarch64 installs that predate the package list,
the Brave Widevine link and the Broadcom migration's Apple Silicon guard never
run anywhere. The exit-75 deferral in omarchy-migrate existed only for two of
them. Keyboard backlight auto stays on new installs through first run rather
than switching on for existing ones, and migration 1786605598 goes back to
quattro: the mkinitcpio drop-ins arrive by package.

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

* Drop the generic changes Mac support doesn't need

The pacman platform guard (and the detector copy omarchy-settings shipped for
it, which the HOOKS baseline fell back to), the lint against uname gates, first
run staying pending after a failed finalization, run_logged echoing failures to
stderr, half-up battery rounding, hiding empty-jack inputs and leaving a
filter's capture alone when choosing an input all rode along with the Mac work
without serving it. Each goes back to quattro's behaviour and can come back on
its own merits.

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

* Drop the platform Hyprland slots

A platform package could load its own Hyprland binds, settings and gestures:
binds that replaced Omarchy's chords and decorated every later bind, settings
ahead of the theme, and gestures after the user's files, which needed every
hl.gesture call wrapped so a default could step aside. On a Mac those carried
macOS habits (a three-finger workspace swipe, tap-to-click off, menus opening
on the built-in screen, other screenshot and keyboard-backlight chords), not
anything the hardware needs, and Omarchy should behave the same on every
machine. The one bind a Mac does need, its lid switch under the SMC's name,
joins Omarchy's own lid binds.

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

* Keep aarch64 machines off the release channels until a release supports them

Every aarch64 channel's template pointed at the edge repository, but a switch
to stable or rc installs the release-named omarchy and omarchy-settings. On
edge/aarch64 those are the 4.0.4 release line, which has no omarchy-hw-platform,
lifecycle dispatch or omarchy-pkg-defaults, so `omarchy-channel-set stable` on
a Mac replaced the quattro runtime it runs with one that cannot run it, and
succeeded.

aarch64 platforms now have edge templates alone, so a switch to stable or rc is
refused before anything changes; adding those templates once a release
supports aarch64 opens them. Refresh with no channel named, as reinstall runs
it, and install finalization for a channel the platform has no templates for
take the platform's default channel: stable on x86_64, edge on aarch64.

* Leave a filter's own capture alone when choosing an input again

d0b94d98c returned omarchy-audio-input-set-default to moving every source
output, as not serving Mac support. It does: asahi-audio's microphone DSP on
Apple Silicon records the raw array through a filter-chain capture stream
(audio_effect.jNNN-mic, node.link-group "filter-chain-..."). On an M1 Pro,
choosing the Mac's microphone in the audio panel moved that stream onto the
DSP's own output, and the microphone recorded digital silence (-91 dB, from
-55 dB of room noise) until PipeWire restarted. With the skip it keeps
recording (-54 dB).

This restores the skip of source outputs carrying node.link-group, and its
test, as they were before that commit.

* Say aarch64 offers edge alone where the templates are described

The aarch64 edge templates, the template helper and the update-process docs
still said every platform has a template per channel and every ARM channel
installs from edge, which the previous commit made untrue.

* Point the Mac packages at omacom/omarchy-mac-pkgs

omarchy-mac and omarchy-mac-boot now live in omacom/omarchy-mac-pkgs, with
each package at the top level. The dispatcher, its documentation and the
Apple Silicon package list name that repository, and the drop-in test's
fixtures, byte for byte the same as omarchy-mac-boot/files/etc/mkinitcpio.conf.d
at 4399105, cite it there.

* Stop naming a platform migration among the dispatcher's callers

The migrate operation went with the Mac migrations, and no caller hands a
machine to a platform migration any more, so the header lists only the
flows that still dispatch. The optional-operations comment is rewrapped.

* Keep the Intel Mac Broadcom migration off Apple Silicon again

A fresh install marks every migration done only for the user it creates, so
an account added later runs them all on its first update. On an Apple
Silicon Mac this one matches: an M1 Pro reports sys_vendor "Apple Inc." and
a BCM4387 (14e4:4433), so it would append feature_disable=0x82000, which
stops that chip scanning once brcmfmac reloads. Restore the guard and its
test case.

* Say when Steam's post-install hook runs now that the drivers go first

Steam's installer now adds the 32-bit drivers before Steam itself, so the hook runs after both and before the first launch.

* Release fallback audio after the recorder exits

When wf-recorder ends outside the normal Stop path, status rejects the saved PID before unloading the combined-audio loopbacks. Release those owned modules when the saved recorder is gone, so the next status or stop call ends microphone capture without signaling another recorder. Leave a mix without a saved PID alone: its recorder can still be starting.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Codex Medium <noreply@openai.com>

* Serialize recording state changes and stale audio cleanup

Status can finish checking an exited recorder after another command has replaced its audio modules, and stop finalization can outlive the recorder while a new recording starts. Share a lock across start, stop and cleanup so each command finishes updating its resources before another changes them. Status uses a read-only check when the lock is busy, keeping the bar responsive during the picker. Close the lock descriptor in persistent children so recording and preview cleanup cannot retain it.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>

* Keep automatic and manual keyboard brightness updates together

A brightness key can update the LED and its chosen-level record after the automatic loop checks for a manual change but before it applies a new target. The automatic write then overwrites the user's choice and deletes the record. Take the manual command's lock for the whole tick, skipping a busy tick and releasing the descriptor on every return. Create the private fallback directory before opening the lock outside a session runtime directory.

The normal lock screen waits five seconds before blanking, so an ordinary tick should already see the session locked. The regression exercises a brightness key arriving within a tick instead of relying on an implausibly delayed lock-state check.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>

* Read the split hook baseline in the NVIDIA migration

The NVIDIA firmware migration decides whether to rebuild by sourcing the hardware and filter drop-ins. With HOOKS now defined in the earlier baseline, that partial read treats every hybrid machine as if kms had been removed. Source the new baseline first when it exists, keeping the legacy two-file layout working and skipping rebuilds when the effective hooks still include kms.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>

* Use a hardware helper for x86-only menu entries

Keep the x86_64 availability checks for Windows and vendor-specific installers in a hardware predicate, so menu conditions use the same command style as other hardware checks.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>

* Keep restored keyboard levels from pausing automatic brightness

Idle wake, quick unlock and hibernate can restore a saved level older than the ambient loop current level without the lock blanking first. Treat that restoration as a one-tick event, preserving existing manual pauses and the normal blank-and-restore path. Consume unmatched and unchanged restore records too, so a later firmware key cannot look like the earlier restore.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>

Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>

* Preserve restored keyboard levels across a later lock blank

The ambient loop skips a locked session, so a restored level may still be unconsumed when the next blank saves it. Carry that origin through blanking and restore failures so fingerprint unlock cannot make the old saved level look like a manual choice. A brightness key replaces the marker with its chosen level and retains the ordinary blank behavior.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>

Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>

* Preserve firmware brightness choices before a lock blank

Firmware keys change the LED without updating the record. Carry a pending restoration through the next blank only if the observed LED still matches that restoration, or is still zero from its previous blank. A firmware change otherwise follows the ordinary blank-and-restore path so automatic lighting pauses on the chosen level.

Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>

* Change only the disk password in omarchy-drive-password

Changing the system disk's password also set the login and root passwords
to match. That changes account-password policy on every platform, x86
included, and overwrites passwords someone chose to keep different, so it
moves to its own change with an explicit confirm.

The disk change itself stays as it was: the new key must open the disk and
the old one must not before the change counts, the journal finishes an
interrupted change, and where the platform's boot checks prove the key
slots, the owner's new slot is recorded before the run finishes. The
journal's confirmed phase is now "record" and no longer needs a login user.

* Cancel a pending recording start instead of queueing behind it

Starting a recording holds the recorder lock through the picker and until the
recorder creates its file. A second toggle or a stop then waited on that lock,
with no end while a portal pick or a stuck recorder never finished, and once
the start gave up the queued toggle began another recording.

While a start is pending, a toggle or stop that cannot take the lock within a
second now leaves a cancel marker and returns. The start checks it after the
picker, after the webcam overlay and while waiting on its recorder, then stops
that recorder and releases its audio. A recorder other than the portal's that
has not created its file after 15 seconds is stopped and the next backend
tried.

* Move automatic keyboard lighting to its own change

Ambient-light keyboard backlight is a new feature on every laptop with the
sensor, not something Apple hardware needs to run Omarchy, so it leaves this
change for a separate review. The keyboard-brightness command returns to
upstream's version, and first run no longer enables the user service.

* Move workspace refocus on monitor removal to its own change

Keeping the viewed workspace in front after a monitor is unplugged changes
desktop behaviour on every machine, not just Apple hardware, so it leaves
this change for a separate review.

* Move prebuilt-image hardware deferral to its own change

Building an image away from its machine needs a manifest the detector reads,
a queue of hardware steps for the first boot, a first-boot service and a
per-machine pacman keyring. An install on the machine itself needs none of
it, so it leaves this change for a separate review with an installer and
package boundary of its own. Hardware and user setup now run where they are
installed, and the platform setup operations take no image argument.

* Move the Qualcomm platform to its own change

Snapdragon support is separable from Apple compatibility, so the detector no
longer names a qualcomm platform: a "qcom," device tree is generic aarch64,
which Snapdragon already behaves as everywhere else here, and a tree naming
both Apple and Qualcomm is still refused. linux-firmware-qcom's platform list
leaves with it.

* Keep a cancel that arrives while a recording start finishes

A stop could land after the start's last cancel check, while it saved its
recording as started, and be dropped with the marker. The start now removes
its starting marker before a final check, a request reads that marker only
after leaving its own, and one that finds the start gone waits for the lock
and acts unless the start already used its request. A recorder ended by a
cancel or a readiness timeout gets two seconds after TERM before KILL, so a
recorder ignoring TERM cannot hold the start and its lock.

* Defer an image build's hardware setup to the machine's first boot

Brings back prebuilt-image support split out of #14431 so it gets its own review. An image built away from its machine names its target in a root-owned manifest the detector reads; hardware setup is queued for the machine's first boot, run there by omarchy-provision-hardware, which also makes the machine's own pacman keyring; and the platform setup operations learn they run on an image's first boot. Apple Silicon images built by the omarchy-mac installer need this; an install on the machine itself does not.

* Rebuild the initramfs when deferred setup changes module options

mkinitcpio's modconf hook copies /etc/modprobe.d into the initramfs, but the
deferred first-boot setup only compared mkinitcpio's configuration and the
kernel command line when deciding whether to rebuild. A step that only wrote
module options, as nvidia.sh does, left the old ones in the boot image.

* Put the Omarchy repository first on aarch64, as on x86_64

quattro now searches [omarchy] before the Arch repositories (34016cfb2), and
its migration 1791403252 moves the section first in /etc/pacman.conf on every
machine. The aarch64 edge template still listed it last, so refreshing pacman
on a generic aarch64 machine would undo that migration. Apple Silicon's
template already lists it first.

* Refuse disk password changes that recovery could not settle

An interrupted change to the system disk is finished from the one key slot
the password opened. If the old password also sat in another slot, a LUKS2
change made in place and stopped before the old key was retired let the rerun
accept the new password as the old one and finish while the other copy still
opened the disk. A system disk whose current password opens more than one slot
is now refused before anything changes, naming the extra slot to remove and,
where the platform records key slots, how to record the ones kept.

The journal now notes that check (checked=1). A change journaled without it,
by an earlier version, on a disk that has other key slots may still hold a
copy of the old password, and no password entered now can show which slot is
whose, so it is left for the owner to settle by hand, in every phase, with the
commands to do it. On a disk with one slot it settles as before.

A new password that already opens a key slot is refused on every drive too:
the new key's slot is how the change is followed, and an existing copy made
the run take the wrong slot as the owner's. A settled interrupted change now
says the disk opens with the password just entered, which is true whether it
finished the change or rolled it back.

* Ignore the start marker of a recording start that was killed

A start killed before it removed its marker left it behind, and a later toggle that had to wait for the lock took that marker for a start still in progress and exited without doing anything. The marker now holds the starting process's pid and counts only while that process runs.

* Say why generic aarch64 searches Omarchy's repository first

Putting [omarchy] ahead of Arch Linux ARM follows quattro's x86_64 order and its migration 1791403252, which already reorders existing aarch64 machines. It means a package Omarchy also publishes now comes from Omarchy, and a refresh can move it there, downgrading it if Omarchy's build is older. The update-process notes and the template now say so.

* Keep a working key in the manual settlement steps

When an earlier version's interrupted change has to be settled by hand, the steps now start by finding and keeping the slot of the password to keep, test the other slots with passphrases only, never remove the kept slot, and record a Mac's slots before the journal goes. Before, removing every slot the old password opened could remove the only key when the change had not landed.

* Keep Superwhisper's x86_64-only default off aarch64

quattro now ships Superwhisper as the default dictation backend and puts
superwhisper-bin in the base list, but it has no aarch64 build. On an aarch64
machine omarchy-reinstall-pkgs and omarchy-upgrade-to-quattro asked for a
package no repository carries, Setup > Defaults offered an installer that
cannot work, new users were preset to that backend, and the dictation keys, F9
and Right Alt (AltGr on many layouts) included, were bound to it.

install/omarchy-x86_64-only.packages names the base packages only x86_64
builds. omarchy-pkg-defaults leaves them out of every aarch64 platform's set,
and omarchy-upgrade-to-quattro out of the base list it installs there. The
Superwhisper default row shows on x86_64 only, like the other x86_64-only
rows. User setup on aarch64 removes the shipped Superwhisper preset, so a new
user starts with no backend and picks one; any other selection stays.
The dictation keys are bound only once the selected backend's own program is
installed, which changes nothing where it is.

* Install the stub dictation backend where binding tests expect its keys

The dictation keys are now bound only once the selected backend's program is installed. The binding-conflict check selected Voxtype without providing it, so it no longer saw the dictation keys and failed its F9 check; the default-config check now also covers a selected backend that is not installed.

* Make the stale start marker test's dead pid reliably dead

The test killed a backgrounded sleep to get a pid that no longer runs. When the signal landed before sleep started, wait never returned and the suite timed out, about one run in three. A process that exits on its own gives the same dead pid without the race.

* Drop the virtual input level meter the panel ran through a helper

Volume and mute for an untyped input still go through wpctl; typed inputs
keep Quickshell's peak monitor. Quickshell's monitor takes typed nodes only,
so the panel hides the level bar for a virtual input rather than showing an
empty bar that reads as a silent mic.

* Find recording processes with pgrep instead of a Python /proc scan

pgrep matches the kernel's 15-character process name, so gpu-screen-recorder
is matched as gpu-screen-reco. That prefix also lets any gpu-screen-reco*
executable, or a process that renames itself, pass for a recorder, so each
match is confirmed against its /proc exe link, ignoring the " (deleted)"
suffix an upgrade leaves.

--signal re-checks that link just before signalling each recorder, which
narrows the pid-reuse window the old pidfd-based scan closed, and exits 1 when
no signal was delivered. --pid rejects values above the Linux pid limit
instead of letting bash arithmetic wrap them.

* Leave 1Password's software GL wrapper to its own change

This reverts f200105c2; the render-GPU check, Electron GL wrapper and desktop entry repair return in a separate branch.

* Keep Gliff's x86_64-only package off aarch64

quattro added gliff to the base list and a migration that installs it, but its recipe builds for x86_64 alone. It joins Superwhisper in install/omarchy-x86_64-only.packages, so aarch64 package sets leave it out, and the migration installs it on x86_64 only.

* Stub accessory enrollment in the first-boot re-key test

Owner setup now enrolls the owner's USB devices and Thunderbolt accessories after the staged LUKS key is gone, and the re-key test's provisioning run reached that step with nothing to answer it. It now stubs both and checks that enrollment runs once, after the staged unlock is retired.

* Keep USB authorization from boot off Apple Silicon

Blocking USB from the start of kernel boot writes Limine's entry-tool drop-in
and checks the Limine menu and UKI directly, but a Mac's boot chain belongs to
its boot package. omarchy-setup-security-usb-authorization --boot is refused
there before anything changes, and Setup and Remove > Security hide USB at
Boot. The boot helper itself refuses enable on Apple Silicon and treats
disable as done when nothing is configured, so turning USB device
authorization off still works on a Mac; a boot-time setting configured some
other way is left with steps to remove it by hand rather than reported
removed. USB device authorization after login is unchanged.

* Skip fixture-only checks when the shell suite runs as root

Root's locale step always writes the real /etc/locale.conf and locale.gen, and root's platform check always reads this machine, so their fixtures do not apply there. The locale test now stops after its static checks as root instead of changing the runner's locale, and the Apple Silicon refusal in the USB authorization test runs unprivileged only.

* Fetch aarch64 packages from Omarchy's HTTPS Arch Linux ARM mirror

* Name platforms x86, aarch64 and aarch64-apple

omarchy-hw-platform now prints the CPU architecture, then the hardware
family when it needs handling of its own: x86, aarch64 (every ARM machine
without a family) and aarch64-apple (Apple Silicon). A later family such
as aarch64-qualcomm or aarch64-n1x follows the same form, so aarch64*
always means ARM.

The Apple predicate is now omarchy-hw-aarch64-apple; omarchy-hw-apple-silicon
stays as a hidden alias while omarchy-mac's packages and units call it.
The Apple package list and pacman templates move to
install/omarchy-aarch64-apple.packages and default/pacman/aarch64-apple/.

Image manifests and omarchy-pkg-defaults still accept apple-silicon,
generic-aarch64 and generic from builders written before the names
settled, and the mkinitcpio baseline accepts a detector that still says
apple-silicon while omarchy-settings and omarchy update apart.

* Add omarchy-hw-aarch64 and gate architecture checks on the helpers

omarchy-hw-aarch64 is the counterpart of omarchy-hw-x86: true on every ARM
machine, Apple Silicon included. The Windows VM asks omarchy-hw-x86 instead
of reading uname itself, and the keyring refresh asks omarchy-hw-aarch64, so
an x86 update no longer runs the platform detector for it.

* Document platform names, helpers and the consistency rule for agents

AGENTS.md gains a Platforms section: the x86 / aarch64 / aarch64-<family>
naming standard, which helper answers architecture and which answers
hardware family, where platform-specific packages, templates, setup and
boot code live, and that every platform should get the same experience,
with missing packages fixed upstream before any workaround and alternative
packages kept as a last resort.

---------

Co-authored-by: Marcelo Alcantara <maralc@gmail.com>
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Scott Jones <scottajones@gmail.com>
Co-authored-by: Marcelo Alcantara <marcelo.alcantara@bp.com>
Co-authored-by: Oliver Lukschander <33756270+oliverlukschander@users.noreply.github.com>
Co-authored-by: Caleb Marshall <68087341+cajomar@users.noreply.github.com>
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
Co-authored-by: Issac Trotts <15530+ijt@users.noreply.github.com>
Co-authored-by: Gabe Ohlson <3355909+6abe@users.noreply.github.com>
Co-authored-by: Sparsh Kumar <22640187+skkumarsparsh@users.noreply.github.com>
Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>
Co-authored-by: kx0101 <90087463+kx0101@users.noreply.github.com>
Co-authored-by: Jim Martin <1334144+jdvmi00@users.noreply.github.com>
Co-authored-by: Ryan Hughes <ryan@heyoodle.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-09 20:15:56 -04:00
David Heinemeier HanssonandClaude Fable 5.1 9d48fc402e Install gliff by default and show when a remote session is active (#14712)
* Install gliff by default

Gliff is the Hyprland remote desktop over SSH. Ship it in the base
package set for new installs, with a migration for existing ones.

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

* Show a bar indicator while a gliff remote session is active

The machine being controlled should make it obvious that someone is on
it. A first-party remote-session service watches Hyprland's screencast
events, which gliff-server triggers through ext-image-copy-capture, and
probes for the server process, picking up the ssh client address from
its environment. The RemoteSession indicator in the default tray lights
up in the urgent colour while a session runs, and a notification
announces each session start and end.

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

* Address review of the remote session indicator

Probe only the current user's gliff-server processes, forward refresh
through the third-party service proxy so cloned indicator widgets can
re-probe, exercise the Hyprland screencast event path in the service
test with a grim capture instead of calling refresh by hand, and
describe the indicator as status only in the manual.

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

* Drop the remote session notifications

The bar indicator is the signal; a toast on every connect and
disconnect is noise on top of it.

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

* Tighten remote session detection after review

Hyprland reports a screencast stopped as soon as frames pause for half a
second and says nothing more when the server later exits, so poll every
two seconds while a session is active instead of trusting a stop event.
Match only the screencast event, not its screencastv2 twin, and debounce
the burst around one transition so it costs one probe. Move the probe
into its own script that the service and the test both run, poll for
state in the service test instead of fixed delays, and drop the unused
session count.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 17:02:42 +02:00
David Heinemeier Hansson 988f44ea1a Delete extra themes from the theme picker (#14713)
* Delete extra themes from the theme picker

Delete on an extra (user-installed) theme opens the shared faded confirmation dialog; confirming runs omarchy-theme-remove and selection lands on the theme before the deleted one. Stock themes and user overrides of them are not offered for deletion, apply/re-ask are blocked while a removal runs, and row refreshes queue so a deletion never leaves the list stale.

* Fail closed on theme deletion until stock themes are known

Keep Delete disabled until the stock listing finishes successfully, so a stock override cannot look deletable in the startup window. Also hold theme apply (and further delete asks) from removal exit until the refreshed rows load, so Enter cannot apply the just-deleted theme in the gap.
2026-10-09 13:18:28 +02:00
David Heinemeier Hansson fcf9eeb5c4 Show the image picker instantly, with no backdrop (#14709)
Remove the carousel's fade and geometry transitions, hold the reveal until every visible preview has decoded and two frames have pre-rendered (falling back to revealing anyway after 400ms), and drop the fullscreen scrim and its theme tokens so the picker sits directly on the desktop. Only Escape is accepted before the picker is visible.
2026-10-09 12:21:45 +02:00
David Heinemeier Hansson 7f95505dec Slow down the reveal effect a little on background changes 2026-10-09 11:21:23 +02:00
be052339d8 Fix lopsided cursor highlight on network panel header actions (#8787)
* Fix lopsided cursor highlight on network panel header actions

The QR and speed-test header buttons used raw Button components with
asymmetric horizontal/vertical padding, producing a highlight box wider
than tall around their near-square icons. Swap them for
PanelActionButton, the canonical square inline-action component already
used elsewhere in the kit (e.g. connectPwBtn), which sizes itself
symmetrically around the icon.

Fixes #8782

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

* Center icons in PanelActionButton on whole pixels

A fractional size and anchors.centerIn put the glyph on half-pixel
coordinates, so it rounded a pixel off the center of the fill: the
speed-test icon sat low in the network header. Round the size, make it
odd so odd-width glyphs get even margins, and place the icon on whole
pixels.

Co-authored-by: Fabian Behrendt <7931874+fabianbehrendt@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Fabian Behrendt <7931874+fabianbehrendt@users.noreply.github.com>
2026-10-09 10:08:26 +02:00
David Heinemeier Hansson c24889096e Add selectable Voxtype and Superwhisper dictation (#14552)
* Add selectable dictation backends

* Move dictation migration after repository priority update

* Extract backend-specific dictation installers

* Keep dictation installer dispatcher executable

* Call dictation backend installers directly

* Drop legacy Superwhisper migration and runtime cleanup

* Separate dictation defaults from generic recording protocol

* Add backend-specific dictation removers

* Preserve existing Voxtype selection during upgrade

* Make dictation backend selection safe and retryable

* Show the dictation indicator only for selected Voxtype

* Allow switching away from stopped Superwhisper

* Remove the built-in bar dictation indicator

* Store default dictation selection under defaults

* Rename dictation backend selector to use

* Move dictation removers into their own menu category
2026-10-08 20:37:26 +02:00
David Heinemeier Hansson b83d3df084 Qualify shell palette references for Qt 6.12 (#14553) 2026-10-08 10:27:08 +02:00
902fd8aebd Play matching background intros at boot and on theme switches (#9639)
* Play matching theme background intros at boot

* Add Tokyo Night sunset lake boot intro

* Stage Tokyo Night boot intros on update

* Add user controls for background boot intros

* Optimize Tokyo Night boot intro videos

* Add Tokyo Night cityscape boot intro

* Add Ristretto boot intro videos

* Add Solitude boot intro videos

* Add White boot intro videos

* Add Vantablack boot intro videos

* Add Miasma nature boot intro

* Add Matte Black boot intro videos

* Add Lupine wave boot intro videos

* Add Last Horizon blink boot intro

* Add Everforest tree tops boot intro

* Add Ethereal cosmic boot intro

* Add four Retro 82 boot intros

* Add Tokyo Night Quattro boot intro

* Add Catppuccin Latte color fade boot intro

* Add five Gruvbox boot intros

* Expand Retro 82 boot intro coverage

* Complete Ethereal boot intros

* Complete Retro 82 boot intros

* Stage boot intros for the active theme

* Add Catppuccin boot intros

* Complete Tokyo Night boot intros

* Expand Lupine boot intros

* Add Osaka Jade mountain boot intro

* Prime boot intros before revealing wallpaper

* Complete Osaka Jade boot intros

* Keep kernel migration test valid after timestamp reuse

* Shorten packaged boot intros to six seconds

* Revert "Shorten packaged boot intros to six seconds"

This reverts commit 5530403aa9e789b977a1a1a77ea7acf37a9e3598.

* Document five to seven second intro target

* Ease Matte Black intros into six seconds

* Play boot intros through OWE instead of a shell player

The merged desktop video path removes BackgroundVideo.qml and makes
BackgroundMedia image-only. This keeps the intro feature by resolving the
matching intro as before and playing it with 'owe intro', which owns the
decode and the reveal. OWE 0.2.3 adds that command. The shell holds the
theme color while resolving and cancels with 'owe intro-stop'.

* Make boot intro startup atomic

* Retry boot intro through login window

* Document required OWE intro protocol

* Add Last Horizon video boot intros

* Play boot intros with released OWE

Use `owe intro` instead of the unreleased intro-prepare/intro-commit
protocol from omacom/owe#5. The boot is recorded before playback, and
OWE's intro_result status decides whether a failed intro never started
(release the boot for retry) or was interrupted (keep it consumed).

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

* Replace Matte Black boot intros with shorter cuts

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

* Hide the still behind black until the boot intro starts

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

* Shorten the Everforest boot intro

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

* Document that boot intros should open dark

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

* Refine the Osaka Jade boot intros

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

* Skip compositor fades on the background layers

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

* Shorten the Catppuccin Latte boot intro

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

* Shorten the Catppuccin boot intros

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

* Shorten the White boot intros

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

* Shorten the Vantablack boot intros

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

* Shorten the Gruvbox boot intros

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

* Shorten the Retro 82 boot intros

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

* Shorten the Solitude boot intros

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

* Rework the Ristretto boot intros

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

* Shorten the Tokyo Night boot intros

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

* Complete the Lupine boot intros

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

* Complete the Last Horizon boot intros

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

* Complete the Miasma boot intros

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

* Add the Nord boot intros

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

* Add the Rose Pine boot intros

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

* Add the Flexoki Light boot intros

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

* Add the Hackerman boot intros

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

* Add the Kanagawa boot intros

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

* Add the Lumon boot intros

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

* Fade in the Last Horizon intros from black

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

* Animate the Catppuccin boot intros from their wallpapers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the third White boot intro from its wallpaper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the Flexoki Light, Lumon and Rose Pine plants intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Shorten the Ethereal boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the remaining placeholder intros from their wallpapers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Replace Catppuccin intros with themotion scenes

Totoro moonrise, waves swelling from a line, and the blue eye tracing
itself open, rendered by https://github.com/yamz8/themotion
(scenes/catppuccin/*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Re-encode Catppuccin intros with exact final frames

Same motion; the settled frames are encoded at a fixed high quality so
the handoff lands on the wallpaper (themotion 0c5e86c).

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

* Replace Lumon intros with themotion scenes

United in severance draws its lines and globe with a small pen-tip
light; Opinions equally wakes inside the picture's own screen and types
out the mark and tagline. Both keep the wallpaper's proportions, so the
handoff holds its shape. Made with https://github.com/yamz8/themotion
(scenes/lumon/*).

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

* Land the Last Horizon intros exactly on their wallpapers

Same footage and timing; the last 0.4 s now blends into the wallpaper as
the shell draws it (libwebp decode, BT.709 limited range), with the
settled frames at high quality. Re-finished with themotion fit (09f156e).

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

* Add Nord video boot intro for Night Hawks

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

* Replace Rose Pine intros with themotion scenes

Funky shapes pop in on a jelly spring and the doodles draw on (replacing
the reversed stock clip), the dot map spins in from a swelling dot, and
the plants spring up before the OMARCHY letters drop in. Made with
https://github.com/yamz8/themotion (scenes/rose-pine/*).

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

* Replace Hackerman intros with themotion scenes

The synthwave terrain unfolds as the sun rises and the valley glow
flares into the floor; the geometric network bursts out from a triangle
at its core and locks into place. Made with
https://github.com/yamz8/themotion (scenes/hackerman/*).

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

* Replace White 3-white with a themotion scene and land 1-2 exactly

3-white folds and slides its paper sheets into place along their
diagonal (themotion scenes/white/3-white). 1-white and 2-white keep
their footage and timing; their last 0.4 s now blend into the wallpaper
as the shell draws it (themotion fit).

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

* Replace Ristretto 0-1 with themotion scenes and land 2-3 exactly

0-launch lifts its stripes off and bends them into their curves, and
1-color-curves fans its colour bands in along their curves (themotion
scenes/ristretto/*). 2-coffee-beans and 3-industrial-moon keep their
footage and timing; their last 0.4 s now blend into the wallpaper as
the shell draws it (themotion fit).

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

* Replace Vantablack intros: new layer scenes, shared dot-hands cut

0-dot-hands uses the Matte Black cut, since both themes ship the same
wallpaper. 1-twisted-stairs keeps its footage and lands exactly on the
wallpaper (themotion fit). 2-layers-deep opens its depth front to back
and 3-layers-stacked lays its papercut stack down sheet by sheet
(themotion scenes/vantablack/*), replacing footage that opened on
unrelated shapes and a plain fade-in.

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

* Drop the shockwave ring from the Totoro intro

The moon now opens from its seed of light with only its halo; the thin
ring racing outward read as clutter (themotion 6970484).

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

* Drop the shockwave ring from the blue-eye intro

The eye closes with its pulse and halo only (themotion, same change as
Totoro's).

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

* Land the earlier footage intros exactly on their wallpapers

Everforest, Osaka Jade, Catppuccin Latte and two Matte Black intros
were finished before the exact-ending tooling and ended 0.9-4 levels
off their wallpapers (up to 134 on small spots). Their footage and
timing are unchanged; the last 0.4 s now blend into the wallpaper as the
shell draws it (themotion fit).

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

* Fade two Matte Black intros up from black

ship-at-sea and dark-waters opened at brightness 17-20, so they popped
in after the black cover. They now fade up over 0.3 s; the cuts and
their exact endings are otherwise unchanged (themotion fit --fade-in).

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

* Drop the Flexoki Light intro for now

Its orb scene isn't good enough yet, so Flexoki Light starts on its still
until a better intro is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bfe60d6c0a7a0983509a2dc2b295693bec2ceeb8)

* Skip boot intros while animations are off

A VM starts with animations off, and there Hyprland renders on the CPU, so a
video intro costs the most. The launcher now consumes the boot without playing
when Hyprland reports animations:enabled false, and the shell's cover check
treats that, like the intros toggle, as nothing to wait for.

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

* Add Miasma video boot intro for Crowned

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

* Keep boot intro startup outside the background service

OWE destroys and recreates the background plugin during playback. Keep the launcher and black cover in the persistent shell host, consume the boot before waiting briefly for OWE, and start on the video's first frame. The startup check runs with a video background too, so enabling the still background service later cannot play an intro over an established desktop.

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

* Release the boot marker lock before waiting for OWE

The marker settles startup once it is written. Keeping the lock through readiness and playback makes a fresh shell wait for an older launcher before it can discover that the boot was consumed, leaving the still covered during that wait.

* Keep the boot intro cover above native and cloned backgrounds

Cloned backgrounds receive a scoped shell interface without the startup coordinator. Keep the input-transparent cover in the persistent host so every background stays hidden until OWE has a frame, without widening the plugin interface.

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

* Map the startup cover only for an unsettled boot

The cover must know the boot was already consumed before its first frame, so restarting the shell during playback leaves OWE visible. Snapshot the marker without watching subsequent writes: the launcher consumes this boot before the intro starts, while the cover remains needed until the first decoded frame.

* Exercise automatic boot cover release in the clone test

The fixture hid the cover itself before the fake launcher exited, so a broken exit handler still passed. Let the launcher finish before checking the actual background pixels and leave cover release to the service.

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>

* Finish the Miasma, Kanagawa, Ethereal and Nord intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Miasma Crowned: ejuro's intro, shortened to 6.7 s and opening on the lit hand
- Nord Night Hawks: ejuro's intro
- Kanagawa: rendered in the wallpaper's own shape, so the handoff matches on
  16:10 and 3:2 screens too
- Ethereal, Nord and Miasma Crowned: 1080p endings that blend into the exact
  wallpaper, so the handoff no longer sharpens visibly

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

* Finish the Tokyo Night intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Winding Road: a new starry night to sunset with no added clouds or sun,
  ending on the wallpaper
- Quattro: a new take where the car lands in the wallpaper's exact pose,
  colour-matched to the wallpaper
- Oma Cityscape: drop the second, larger moon that faded out at the end
- Quattro, Swirl Buck, Omakub and Oma Cityscape: 1080p endings that blend
  into the exact wallpaper, so the handoff no longer sharpens visibly

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

* Pair background intros by filename and play them on theme switches

Keep matching clips in backgrounds/intros so the selected image determines playback without registration, overrides, or checksum sidecars. Retain one global toggle and play automatically at boot and on a different theme selection.

Launch theme playback after releasing the selection lock. Check the staged theme directory identity so an older launcher cannot play a newer selection when filenames are reused. Skipped or failed playback immediately hands the selected still to the shell, including new pixels behind an unchanged path.

Synchronize the clone screenshot fixture with capture instead of a fixed launcher lifetime, while still requiring the real exit handler to release the cover.

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

* Settle pending background colors when applying the current theme

An intro applies its theme immediately while a previous still-image reveal may still hold an older palette. Replace and consume that pending palette through the background's existing handoff methods so its reveal or fallback timer cannot restore old colors. Reusing those methods also works with existing background clones.

Publish the fake renderer's PID before announcing playback readiness, so the lock test cannot read an unwritten PID file. Regressions reproduce both failures on 7b238cf1 and pass with the fixes, including a deliberate scheduling delay at PID publication.

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>

* Use the existing animations menu toggle for background intros

Keep the separate intro command available without another Style menu control. Disabled animations already suppress playback at boot and on theme changes.

Use remembered backgrounds with matching clips when testing suppressed theme intros, and require the normal still transition so the checks cannot pass merely because no clip was selected.

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

* Finish the Lupine intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Cherry Blossom White: a new take that pulls back from fresh leaves to the
  whole tree, easing to a stop on the wallpaper
- Pastel Clouds: drop the unrelated starry opening and its dip to black
- Elegant Blue Wave: drop the empty white opening
- Cherry Blossom Bokeh, Pastel Clouds, Elegant Blue Wave and Cherry Blossom
  White: 1080p endings that blend into the exact wallpaper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Synchronize theme intro and bar crossfades

* Start theme intro fades from a cached first frame

* Open theme intros on moving scenes

* Start theme crossfades promptly

* Prepare theme intros before switching

* Finish the Catppuccin intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Keep theme intro handoffs covered

* Reload Hyprland after theme intro playback

* Finish the Rose Pine intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Restore the wallpaper early on each login

* Keep the startup wallpaper cover transparent

* Wait for readiness in the intro lifecycle test

* Consume login intros for updates outside Hyprland

* Fade login directly into the ready desktop intro

* Sharpen Quattro, Swirl Buck and Oma Cityscape

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

* Keep compositor backgrounds black during login

* Keep the shell visible through compositor shutdown

* Bound the login cover when startup media fails

* Encode the 1440p intros at H.264 level 5.1

At 2560x1440 the previous encodes used 16 reference frames, which puts
them at level 6.0. Intel hardware decoding plays those with stray grey
blocks. Four reference frames keep them at level 5.1, like the other
intros. Swirl Buck's stag now changes colour as one silhouette instead
of leaving a shrinking black disc on its head.

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

* Hide the startup cursor until the desktop fades in

* Restore Swirl Buck's original stag

The stag now changes colour exactly as in the original clip again;
the previous edit made it jump from black to purple in one frame.

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

* Initialize the startup cursor before the first compositor frame

* Render the Hackerman intros at 1440p

Both open on their first motion instead of fading in from black.

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

* Render the Lumon intros at 1440p

Both open on their first motion instead of fading in from black.

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

* Sharpen the Lupine intros

Upscaled from the original 720p clips and exported at 1440p with a
gentler encode, keeping the same frames, timing and endings.

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

* Resolve login intros before the wallpaper daemon starts

* Synchronize cold-start captures with the fixture phases

* Sharpen the rest of the Tokyo Night intros

Winding Road and Sunset Lake are regenerated natively at 1440p from
their wallpapers, since their original clips were 360p and 720p.
Omakub and Oma are redrawn from their wallpapers with the same motion.

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

---------

Co-authored-by: Bjarne Oeverli <1419214+bjarneo@users.noreply.github.com>
Co-authored-by: Erik Johansson <172146456+ejuro@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Omarchy Bot <omarchybot@users.noreply.github.com>
Co-authored-by: Codex Medium <codex@openai.com>
Co-authored-by: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
2026-10-07 22:00:52 +02:00
David Heinemeier Hansson 0a55a3c554 Keep overlays sharp without parked surfaces (#14458)
* Keep overlays sharp without parked surfaces

* Refresh overlay monitor scale after live changes

* Reset menu layout when its monitor changes
2026-10-07 19:42:41 +02:00
David Heinemeier Hansson d9b970dd62 Let users choose passwordless sudo duration (#14435)
* Let users choose passwordless sudo duration

* Warn in the menu bar when passwordless sudo is active

* Fix sudo indicator hover behavior and repeated authentication

* Disable passwordless sudo from the bar without a terminal

* Shorten passwordless sudo indicator tooltip

* Recover interrupted passwordless sudo duration switches

* Allow sudo grant changes when listings require authentication

* Start passwordless sudo setup with the duration question
2026-10-07 14:41:27 +02:00
David Heinemeier HanssonandClaude Opus 5.5 b6f2c1cef7 Describe the agents panel's limit rows and sign-in link as they are (#14430)
The manual still said the panel shows the percentage used and that an idle
account keeps its last known limits, and the README that sign-in trouble is
only text. Rows show the time to reset with the percentage on hover, and a
lapsed sign-in is a Sign-in required link that signs the account in again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:15:38 +02:00
b9e0ac4f1d Prevent clipboard capture hangs (#9488)
* Bound clipboard capture reads

* Bound the watched clipboard read and drop copies cut off at the deadline

In watch mode capture.sh reads the copy from the owner's pipe on stdin, to EOF, with no bound. An owner that stalls without closing its end keeps the callback alive, and wl-paste --watch handles no further clipboard events until it exits. That matches #9443, whose stuck capture.sh had no wl-paste child: --list-types never reads from the owner.

Every read now goes through one bounded reader into a temporary file, and the copy is recorded only when the read finished, so a snapshot that stalls halfway no longer records its first half.

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

---------

Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:58:07 +02:00
David Heinemeier Hansson 5a0e7348af Show Elsewhen remove button only on mouse hover 2026-10-05 14:44:19 +02:00
David Heinemeier Hansson ec76c070d2 Give Elsewhen remove buttons a circular corner bubble (#14258) 2026-10-05 12:42:42 +02:00
David Heinemeier Hansson e86c8f1ab7 Reveal indicators only before the clock (#14267) 2026-10-04 14:00:09 -04:00
879d6583da Fix fingerprint enrollment and lock-screen recovery (#7158)
* Restart fprintd after resume to clear a claim wedged by suspend

A fingerprint verify still open when the machine suspends leaves fprintd
unable to hand the reader back: the verify dies with "Cannot run while
suspended" and the follow-up ReleaseDevice fails on the still-busy device.
The wedged claim then rejects every lock-screen attempt after resume until
fprintd exits on its own 30-second idle timer -- and the retry loop keeps
it from ever reaching that timer, so the reader stays dead until the user
gives up and types a password.

Install a system-sleep hook that restarts fprintd on resume, dropping the
claim so the reader answers on the first touch. It is installed by
omarchy-setup-security-fingerprint and removed by its teardown, so it is
present exactly when a fingerprint reader is configured. try-restart is a
no-op when fprintd is not running, so a healthy resume pays nothing.

Approach suggested in #7229 and measured by @paracycle: 45 stray PAM
sessions after resume down to 2.

* Pace fingerprint retries and show when the reader is unavailable

The lock screen retried fingerprint auth on a flat 250ms timer with no
sign to the user, so a reader it could not reach -- a claim wedged across
suspend, one held by another client, or a sensor gone from the bus --
spun PAM sessions at four per second behind an icon still inviting
touches that could never unlock.

Pace and report on one signal: whether an attempt reached the reader at
all. pam_fprintd relays a finger prompt only once the claim lands, so an
attempt that ends without prompting never reached the device. Those
advance a streak that backs the retry off exponentially (to a ceiling
above fprintd's 30s idle exit) and, past a few in a row, crosses out the
icon and shows a "Fingerprint reader unavailable" notice. An attempt that
did prompt proves the reader works -- a finger that merely did not match
still reaches it -- so it clears the streak and the loop stays responsive.

User presence (a keypress or touch) collapses a backed-off wait to a
prompt retry, rate-limited so a moving cursor cannot respin the storm. An
attempt that never reaches the reader within a few seconds is aborted and
settled as unreached, so a claim orphaned by the resume restart surfaces
the notice and retries a fresh daemon rather than hanging silently.

The pacing, streak, nudge, and reach-timeout logic live in
FingerprintModel.js with Node coverage; the new Text elements declare
textFormat; lock status reports fingerprintUnavailable.

The attempt state machine tracks the open attempt with fingerprintAuthenticating alone; the first settle closes it, and one PAM attempt raising both onError and onCompleted still folds into the streak exactly once.

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

* Install the fprintd resume hook root-owned and keep it with the PAM file

cp -p carried the checkout's owner and mode into
/usr/lib/systemd/system-sleep/, so under dev-link the root-executed hook
was user-owned, and a tree whose exec bit had been stripped installed a
hook that systemd-sleep silently never ran. Use install -Dm755 -o root
-g root, as the migration that installs the same file already does.

The hook also belongs exactly where the fingerprint PAM file does:
omarchy-apply-lock creates and removes /etc/pam.d/omarchy-lock-fingerprint
on its own, and any apply-lock run after enrollment left PAM without the
hook while its removal branch left a hook behind without PAM. Have
apply-lock install and remove the hook together with the PAM file, and
teach apply-lock-test.sh to redirect the hook into its scratch tree and
assert the hardened run lands it beside the PAM fixtures.

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

* Only treat fingerprint as configured when a print is enrolled

The lock screen and omarchy-apply-lock decided fingerprint was set up with
fprintd-list | grep -qi finger, which also matches "has no fingers enrolled"
and "ListEnrolledFingers failed". A second account on a machine where one
user enrolled, or anyone who ran fprintd-delete, was therefore handed the
fingerprint loop: every attempt bailed before the claim, and with the new
pacing that showed up as a crossed icon and "Fingerprint reader unavailable"
for a reader the account simply has no print on. Match the per-print
" - #N:" lines instead.

apply-lock-test.sh follows: its fprintd-list stubs answer with a real enrolled-print row and its helper patcher matches the new probe line.

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

* Exercise the migration's default hook source in its test

Every case overrode OMARCHY_FPRINTD_RESUME_SRC, so the path the migration
really reads from was never checked, while its -f guard turns a missing
source into a clean exit and a permanent per-user marker. Add a case that
runs against the shipped hook under the repo, and adopt set -euo pipefail
like the sibling tests.

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

* Take the fprintd restart off the thaw and bound its stop timeout

The resume hook ran systemctl try-restart synchronously while user
sessions were still frozen, so its cost landed on the wake path: half a
second when fprintd answers SIGTERM, but a wedged fprintd on a stale
device handle (the reader re-enumerated across the sleep) does not, and
then the desktop stayed frozen for the whole stop timeout -- precisely in
the case the hook exists for.

Enqueue the restart with --no-block instead, as the unmount-fuse hook
already does for the same reason, and ship a drop-in capping fprintd's
TimeoutStopSec at 3s so the restart lands within seconds either way. The
drop-in is numbered 10-stop-timeout.conf, as the other Omarchy system
drop-ins are, so an administrator's override.conf sorts after it and
wins. It is installed and removed wherever the hook is (setup, teardown,
apply-lock, migration), and apply-lock-test.sh redirects it into its
scratch tree alongside the hook.

The hook's comments now say what actually happens on a locked resume --
Omarchy locks before every suspend and the lock screen opens a verify at
once, so the restart is real, not a no-op -- and name the upstream
defects this works around, fprintd#173 and fprintd#216, so the hook and
the drop-in can be retired when upstream fixes them.

Measured by MaxMad75 on an X390 Yoga (S3): 2 of 10 fprintd stops rode out
the timeout to SIGKILL; the 3s cap verified with systemctl show.

Co-authored-by: Omabot <omabot@omarchy.org>
Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt

* Close the status-check and start-failure exits through settle

Two paths left the fingerprint loop stuck or misreporting. A mid-lock status check that found fingerprint unconfigured aborted the PAM context directly; abort() delivers no signal, so fingerprintAuthenticating stayed true and every later attempt and nudge returned on it until the password unlock. And a fingerprintPam.start() that fails synchronously means the PAM file is gone -- a configuration problem, not a reader miss -- yet it fed the reader streak and reported "Fingerprint reader unavailable".

Route the abort through settleFingerprintAttempt like the reach timeout does, drop the pending retry with it, and on a start failure re-check the configuration so the icon disappears instead; a pending retry owns the next attempt when a status check comes back configured.

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

* Pace fingerprint nudges by the pending tier and the cap's idle stretch

The nudge cooldown was a flat 2s, shorter than every backoff step, so a
user moving the mouse at a wedged reader collapsed each wait to 2s --
thirty claims a minute against the cap's 1.5 -- and each claim re-armed
fprintd's 30s idle timer, so the hook-less recovery the cap exists for
never happened while anyone was present.

Grow the cooldown with the pending wait, so presence collapses each
backed-off wait once and repeat nudges are paced by the tier. At the cap
the wait itself is the cure -- it is what lets fprintd idle out and drop
a wedged claim -- so there the idle stretch is measured from the last
settle, not the last nudge: a nudged attempt that hung until the reach
timeout would otherwise eat most of the window, and under continuous
input fprintd would never be left alone long enough to exit.

Wall-clock steps are handled in both directions: a clock stepped back
past the last nudge does not hold a fresh nudge back, and one stepped
back past the last settle counts as no idle time at the cap rather than
as enough. The retry test drives continuous input against attempts that
hang to the reach bound and checks the gap fprintd is left.

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

* Give a slow fingerprint claim time to land before aborting it

The reach bound aborted any attempt that had not prompted within 5s by
SIGKILLing the PAM child mid-Claim. A reader whose device open takes
longer than that (out-of-tree drivers, and any reader right after the
resume hook forces a re-open) could then never prompt: each kill left
fprintd tearing the claim down until the open finished, the 1s retry hit
"already claimed", and three misses later the reader was reported
unavailable for good. Raise the bound to 20s, under GDBus's 25s Claim
timeout and pam_fprintd's 30s verify timeout (whose "Verification timed
out" is a non-error message that would read as reached), and name the
hazard the bound actually covers: a daemon restarted under the verify
fails the attempt promptly, a stuck device open does not.

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

* Detect resume and hold the streak through the restart window

Monotonic timers pause across suspend, so a backed-off wait armed before
the sleep picked up mid-count afterwards: with the streak at the cap the
"Fingerprint reader unavailable" notice stayed up for the remaining wait
after the resume hook had already freed the reader, and misses collected
around the suspend edge carried across it, so a healthy reader could
cross the notice threshold in the first seconds after waking. With the
restart enqueued off the thaw, the loop's first attempts after a wake can
also land on the old daemon while it is being stopped -- up to ~3s when
it ignores SIGTERM -- and three of those would show the notice for a
reader that was merely being restarted underneath.

Notice a resume from any of three signals -- a sleep watch ticking the
wall clock for the whole lock, a retry that fired late, or an unreached
attempt whose settle finds the watch's last tick far in the past (so a
suspend shorter than the reach bound is caught before the tick itself
gets a chance to) -- and open a grace window: the stale streak is
dropped, a pending wait retries the fresh daemon at once, and misses
inside the window hold the streak at the first tier without ever counting
toward the notice. Detection is idempotent within the window, since more
than one timer can notice the same resume.

Pinned by MaxMad75's reading: the window is armed by the resume, not by
the first miss. Verified on his X390 (S3, frozen sessions): six lid-close
cycles, fingerprint-resume at +15ms, streak held, notice never fired.

Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com>
Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt

* Only let a definitive probe change whether fingerprint is configured

The status probe collapsed every fprintd-list result into yes or no, so
an unreachable fprintd -- restarting under the resume hook, or failing a
D-Bus activation mid-resume -- read as "not configured": the icon
vanished, the retry loop and the sleep watch stopped, and nothing asked
again for the rest of the lock. One transient miss killed fingerprint
until the next lock, with the password as the only clue. MaxMad75 hit it
on hardware in run 6 of the X390 series; osborng filed the stock repro as
#9453 (mask fprintd, lock, unmask -- fingerprint never returns).

Classify the probe's output instead: an enrolled-print row is yes,
fprintd's explicit no-prints answer (or a missing PAM file or binary) is
no, and anything else is unknown -- the probe could not tell, so nothing
changes and it is retried on the attempt-retry pacing. The unavailable
notice, backoff, and resume detection all sit downstream of this flag;
now only an answer that actually means something can clear it.

Fixes #9453.

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

* Log the fingerprint loop's misses, notice, and recovery as lock events

The reach timeout, an unreached settle, the streak crossing into the
notice, a resume restart, and the recovery all changed lock state without
touching logEvent, so a report of "Fingerprint reader unavailable" left
no omarchy lock line to line up with suspend and resume timestamps in
omarchy-debug-idle output. Log those transitions; reached attempts are
the steady state and stay quiet. A match that unlocks after a run of
misses is the recovery too -- the unlock resets the streak without
settling, so it logs fingerprint-recovered there as well.

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

* Abort an attempt stranded in flight when a resume is detected

A verify that survived into the suspend still prompted comes back to a
daemon the resume hook has already replaced, and the loop's resume
handling deliberately left it alone: the reach timer stopped at the
prompt, so nothing bounded it but pam_fprintd's own ~25s timeout, and
until that ran out the icon invited touches that could not work. Most
visible where user sessions are not frozen across sleep and the lock
races the hook.

Abort the stranded session when the resume is detected and route it
through settle: it lands inside the grace window, so the kill never
counts toward the notice, and the settle arms the fast retry against the
fresh daemon itself.

Suggested by sliekens in review.

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

* Simplify fingerprint recovery and consolidate enrollment checks

Use the enrolled-entry matcher from #9551 while retaining the lock's tri-state probe recovery and the privileged /usr/bin/fprintd-list call. Unknown enrollment probes must preserve existing PAM and resume recovery rather than deleting the machinery needed to recover. Preserve administrator-owned unnumbered timeout files during migration.

Remove presence-driven retry overrides and their cooldown, clock, and idle-window state: resume has its own fast recovery path, while other errors can follow the bounded automatic backoff. Let the existing sleep watcher detect resume instead of also tracking the age of each retry. Setup now uses apply-lock so PAM and recovery installation have one implementation. Keep the restart, stop bound, unreachable-attempt pacing, unavailable feedback, reach watchdog, and probe rechecks because each handles a distinct failure.

Co-Authored-By: Karl Ahlin <kalle.ahlin@gmail.com>
Co-Authored-By: Codex Medium <noreply@openai.com>

* Preserve failed-enrollment coverage in the setup fixture

The successful-enrollment fixture accepts PAM commands, so failure checks must explicitly reject those commands instead of relying on an unexpected-command error. Log both sed and tee and stub apply-lock for every case so premature authentication setup is detected without reaching live PAM files.

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

* Complete fingerprint recovery and setup reporting

Back off immediate device errors after the verification prompt as well as failed claims, while retaining fast retries for mismatches and normal scan timeouts. Measure from the prompt so a slow claim cannot hide a fast failure. Paced user activity retries preserve the daemon idle window required to clear a wedged claim. Initial probe outages remain visible without inventing enrollment, and setup cannot claim lock-screen success when the PAM configuration was not installed. Exercise the real QML service rather than a copy of its state machine.

Co-Authored-By: GPT-6 <noreply@openai.com>

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

* Avoid competing fingerprint probes and partial setup

Known enrollment is recovered by the PAM retry loop, so failed status probes must not raise a false unavailable notice or interrupt its daemon idle window. Initial unknown enrollment still gets paced probes. Install recovery files before enabling fingerprint PAM so a missing source cannot leave a new partial configuration.

Co-Authored-By: GPT-6 <noreply@openai.com>

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

* Keep the fingerprint error clock at the first prompt

pam_fprintd also sends Verification timed out as an informational message. Updating the prompt timestamp on that message made a normal full scan window look like an immediate device error and caused unnecessary backoff. Record the first prompt of each PAM attempt so later status messages cannot move the error window.

Co-Authored-By: GPT-6 <noreply@openai.com>

---------

Co-authored-by: Omabot <omabot@omarchy.org>
Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Karl Ahlin <kalle.ahlin@gmail.com>
Co-authored-by: Omarchy Bot <omarchybot@users.noreply.github.com>
2026-10-04 12:49:44 -04:00
David Heinemeier Hansson 27a3feffc6 Fix icon spacing 2026-10-04 09:10:55 -04:00
454b67d95f Fix the bar startup stall and the shell restart race (#11015)
* Instantiate only the current orientation's indicator tree

Indicators.qml built both the horizontal Row and the vertical Column and
toggled them with `visible`, so every indicator existed twice per bar,
and so did every process an indicator spawns: Dictation.qml ran two
`voxtype status --follow` per monitor. On a six-monitor bar that is 72
indicator instances and twelve followers for six visible icons, and each
instance registers a click target and re-syncs the active-indicator model
as its state resolves at startup.

A Loader now instantiates the tree that matches `root.vertical`. Each tree
is wrapped in an Item that keeps the stock explicit implicit-size
expressions, so the root's size still follows the blocks synchronously; a
bare positioner only updates its implicit size on polish, which the
indicator contract test's center-hover check catches.

Measured on a six-monitor, 23-widget bar (three runs each, `omarchy
restart shell`): time from "Configuration Loaded" to "polkit agent
registered" 19.8-20.3s -> 15.5-15.7s, quickshell CPU 29-30s -> 24-25s,
voxtype followers 12 -> 6.

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

* Coalesce plugin API resyncs and key bar object ownership by target

Every WidgetButton registers itself as a bar click target when it is
created. registerClickTarget replaced the clickTargets array, the change
handler ran syncAllPluginBarApiObjects() inline, and that walked every
plugin API times every click target times a linear scan of
pluginObjectOwners in pluginObjectRecord. Startup is a few hundred
registrations, so the cost is quadratic in bar size and multiplied by the
number of monitors: a six-monitor, 23-widget bar spent 16-20 seconds of
pegged QML thread before it was populated, and an 8-second qmlprofiler
capture showed 1.36 million pluginOwnsBarObject calls and 46-71ms per
registration.

- pluginObjectOwners is a Map keyed by target, so pluginObjectRecord,
  markPluginObject, unmarkPluginObject and releasePluginObjects are O(1)
  per object. Nothing outside Bar.qml read the array.
- The activePopout, clickTargets and layoutConfig change handlers schedule
  one resync per event-loop turn through Qt.callLater, the way
  onModuleSlotsChanged already defers prunePluginBarApis. bindPluginBarApi
  still syncs a brand-new API inline, and requestPluginPopout and
  releasePluginPopout sync the owning API inline, so a plugin never reads
  a stale API on its own actions.
- A flush serialises the layout once and hands each API its own parsed
  copy instead of deep-copying it once per API per sync.
- ModuleSlot's cursorShape read clickTargets for every slot on every
  monitor (26,700 evaluations per start). It is now gated on the slot's
  HoverHandler, which is the only time the cursor is over it.

Measured on the same bar, launching the shell from a checkout with this
and the indicators change (two runs): "Configuration Loaded" to "polkit
agent registered" 19.8-20.3s -> 0.70s, bar populated 1.7s after launch
(from ~30s), quickshell CPU 29-30s -> 2.0s.

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

* Wait for the old shell to exit before restarting it

omarchy-restart-shell stopped the running shell with `quickshell kill`
under a five-second timeout and launched the replacement as soon as the
loop ended. A six-monitor bar takes 5.4-6.0 seconds to tear down (every
widget button unregisters its click target on destruction, and each
unregistration re-synced every plugin API), so the client timed out while
the shell was still exiting, the fresh instance's no-duplicate check saw
the dying one and quit, and the user was left with no bar and "Omarchy
shell did not become ready after restart".

Give the kill client thirty seconds, then wait, bounded, until
`quickshell list` shows no instance of the session config before
launching. The readiness check also waits on a sixty-second deadline
instead of twenty attempts: a large bar answers ping only after its
plugins have loaded, which on the stock bar was well past the old
twelve-second window.

Verified three consecutive restarts against the stock shell on the
six-monitor machine: each returned 0 in about six seconds with exactly
one instance and no "already running" in the journal.

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

* Say what the bar's ownership Map actually saves

Qt's V4 Map (ESTable::get) finds a key by scanning its keys, so ownership lookups are not O(1). The win is that a registration no longer copies the owner array and rescans it in QML.

Co-Authored-By: Codex Medium <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Codex Medium <noreply@openai.com>
2026-10-04 08:09:30 -04:00
David Heinemeier Hansson 2b2d462652 Merge pull request #13725 from houz42/fix/monitor-panel-display-toggle
Fix Display panel display toggle using rejected hyprctl keyword
2026-10-04 08:07:38 -04:00
David Heinemeier Hansson 00cee6d319 Merge pull request #7806 from berndb/lock-ignore-autorepeat
Drop key auto-repeat in the lock screen password field
2026-10-04 07:48:34 -04:00
David Heinemeier Hansson 7901d7d0b6 Merge pull request #12538 from paulogeyer/fix/10860-idle-timeout-zero
Treat idle timeout 0 as disabled, not immediate
2026-10-04 07:44:44 -04:00
David Heinemeier Hansson 4a568a1388 Merge pull request #14225 from omacom/fix/agents-usage-display
Fix agent usage resets, stale display, and icon underline alignment
2026-10-04 07:14:39 -04:00
David Heinemeier Hansson 7f91a8d49e Show recovery guidance when paused usage has no cache 2026-10-04 07:10:41 -04:00
David Heinemeier Hansson 6809987892 Align panel underlines with native bar icons 2026-10-04 07:01:03 -04:00
David Heinemeier Hansson d21e5810b1 Reset elapsed agent usage and show stale ages on hover 2026-10-04 07:01:03 -04:00
Omarchybot 5c4da02146 Merge pull request #7783 from omacom/fix/issue-6952
Keep PwNode objects out of the audio panel's Repeater models
2026-10-04 02:33:47 +02:00
David Heinemeier HanssonandClaude Opus 5.5 18328559b9 Keep the Wi-Fi icon steady on OWE transition-mode networks (#14133)
* Keep the Wi-Fi icon steady on OWE transition-mode networks

An OWE transition-mode network pairs an open SSID with a hidden "_owetm_"
twin on the same BSSID. Between scans NetworkManager reports the in-use
access point under the hidden SSID and drops the active profile from the
device's AvailableConnections, which is the only place Quickshell builds
known networks from. No listed network is then connected, so the bar fell
back to the disconnected icon until the next scan brought the open SSID
back, flickering on and off while the link stayed up.

Fall back to the Wi-Fi device's own connected state when no listed network
is connected, and read the in-use access point's strength from nmcli
(without a rescan) while the connected network has none of its own.

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

* Discard stale in-use AP reads and key Wi-Fi checks on the device

Bump a generation whenever the cached in-use access point strength stops
describing the link (leaving Wi-Fi or a device change), and drop any nmcli
read started before it, re-reading immediately instead of a full interval
later.

Key Wi-Fi connectivity checks on the device rather than the SSID, so the
listed network coming and going with each scan on an OWE transition-mode
network no longer schedules a check. A real network switch still passes
through "disconnected".

Add a QML fixture that runs the panel against a mocked device through the
scan churn, a mid-read disconnect, and a final disconnect.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:05:32 -04:00
Bjarne Oeverli e5663c7e3a Keep image picker previews sharp on HiDPI displays 2026-10-03 18:19:16 +02:00
e0b0f349f8 Escape the output name in the Display panel's hl.monitor eval
Hyprland lets a headless output take any name (`hyprctl output create headless <name>`), so a name with a quote broke out of the Lua string and the toggle failed for that output. Escape backslashes and quotes instead of asserting a character set the names do not actually follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
2026-10-03 12:29:24 +02:00
Ric Lewis a8e09b9ce2 Keep refreshed image picker selection inside the active filter 2026-10-03 00:03:44 -07:00
Ric Lewis 2ba1015ef2 Keep image picker work bounded for large theme collections 2026-10-02 22:45:39 -07:00
Spencer Bull 0537ae121b Merge pull request #11925 from joewinke/fix/plugin-thirdparty-sourcedir
fix(shell): keep __sourceDir on third-party plugin manifests
2026-10-02 21:08:57 -05:00
+14 75250d37ac Fix Codex limits, Claude counting, and agent usage reliability from community PRs (#14049)
* Read Codex app-server replies from the raw fd (#13703)

* Resolve Codex through mise which instead of running the lazy launcher (#13109)

* Skip the Codex app-server probe when there are no credentials (#13106)

Adapted: credentials are checked in the home being probed rather than in
the CODEX_HOME environment variable, since each registered account is
probed in its own home, so a signed-out secondary account isn't hidden
behind the primary's login. A home without credentials reports "Waiting
for auth" like any other signed-out home. The credentials store setting is
read with tomllib, so a single-quoted value counts too.

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

* Show the Codex CLI's own error when its app-server dies (#8977)

Detect an app-server that exits or stops answering, and report the end of
its stderr instead of a bare RPC method name. Rebased onto the raw-fd
reply reader; the switch from "-a on-request" to "-a never" is left out,
keeping the current approval flags.

* Count pi sessions when HOME is a git checkout (#13209)

* Count only OpenAI-backed native sessions as Codex usage (#12032)

* Deduplicate Pi usage across forked sessions (#8602)

* Skip unchanged native Codex token snapshots (#10531)

* Count omp and pi profile sessions in the agent usage collectors (#9546)

`omp --profile=<name>` (and pi's equivalent) relocates the whole agent
tree under <base>/profiles/<name>/. The Claude and Codex collectors only
ever scanned <base>/agent/sessions, so a subscription driven entirely
through a profile was invisible to the agents panel: no tokens by day, no
tokens by model, no prompt or session counts.

Discover the profile roots alongside the default one. Sessions are keyed
by file path, so a profile adds sessions instead of double-counting the
default root, and a missing or unreadable profiles directory leaves the
existing behavior untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zFbJcDEEpV5BAmsH6kAB3

* Skip unrelated Codex session lines before JSON parsing (#12803)

Adapted: session_meta lines also pass the pre-filter, since the provider
filter from #12032 reads them to skip rollouts served by a non-OpenAI
provider.

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

* Read only the Codex session files that changed since the last scan (#12595)

Native Codex rollouts keep per-file totals between runs, replayed while a
file's mtime and size are unchanged. Rebased onto the session_meta
provider filter, snapshot dedup, and line pre-filter, which now live in
the per-file reader. pi and omp sessions are left out of the per-file
cache: a forked pi session repeats its parent's messages, so they are
deduplicated across the whole tree on every scan.

* Count streamed Claude messages by their highest-output usage line (#10606)

Claude Code writes a streamed assistant response as several transcript
lines that share one message id, one per content block. Each line
carries a usage object. The first line's output_tokens is a placeholder,
often 1, and the last line has the real count. Input and cache fields
usually match across the lines.

The scanner dedupes by message id and keeps the first line it sees, so
it under-counts output tokens. On a machine with 2,577 transcripts it
reported 39.0M output tokens against 60.1M used, a 35% shortfall. Input
and both cache fields differed by under 0.01%.

Keep the line with the highest output count, with the last one scanned
winning a tie. The whole line is kept because a response can fall back
to another model mid-stream. Those lines are separate snapshots with
different cache figures and a different model, and taking a maximum per
field across them over-counts cache tokens and credits the wrong model.

The zero-usage check now runs before dedup, so a zero-usage first line
no longer claims a message id and hides a later line with real usage.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: GPT-6 Astra <noreply@openai.com>

* Index Claude transcripts so the agents refresh reads only what was appended (#8313)

omarchy-agent-usage-claude re-parsed every line of every transcript under
~/.claude/projects on each refresh: no mtime cutoff, no memory of the last
pass. The agents widget is on by default and ticks every 15 minutes, so the
cost grew for the life of the machine. After one month here that was 803
files, 640 MB, 127k lines and 57k JSON parses per tick, about 1 core-second,
pushed through the page cache every quarter hour forever.

Keep a per-file index next to the scan cache: the unique usage records
already parsed out of each transcript and the byte offset they end at. A
file whose size and mtime match is not opened; a file that grew is read
from the stored offset; a file that shrank or was rewritten is read from
the start. --force drops the index and rescans from scratch.

The summary is built from the indexed records in the same directory order
the walk always used. That matters: when a resumed session carries earlier
messages, the same message id appears in two files with different usage,
and the first file visited wins. 91 ids differed on this machine; sorting
the walk moved one model's output total by 25k tokens. Output is now
byte-identical to the previous scan on a frozen copy of the corpus, cold,
warm, and after an append.

Warm refresh: 1.0 s -> 0.10 s of CPU, of which the scan itself is 70 ms;
the index for this corpus is 2.9 MB.

Adapted:
- Rebased onto #10606: the highest-output rule for streamed messages now
  lives where the index parses records, and decides between files too.
- The index records the timezone it was written in, and a change rereads
  every transcript, since its records hold local days.
- A file only counts as appended to when its inode and the hash of what
  was already read still match, so a transcript replaced by a larger one,
  or rewritten in place, is read from the start.

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

* Label a Claude Team seat by its subscription, not its rate-limit tier (#11109)

The collector built the plan label from the OAuth rateLimitTier first, so a
Team premium seat, which runs on default_claude_max_5x, showed in the agents
panel as "Max 5x". Lead with subscriptionType and keep the multiplier as its
qualifier: Max still reads "Max 5x", a Team seat reads "Team 5x".

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

* Label the Claude plan from the profile the CLI refreshes (#7225)

Adapted: the profile is found the same way current_account_id() finds it,
now shared as profile_path(): ~/.claude.json for the default home, the
home's own .claude.json otherwise. The original fell back to ~/.claude.json
for any home without CLAUDE_CONFIG_DIR set, so a secondary account read the
primary's tier. The profile's tier also keeps the subscription in the label,
so a Team seat stays "Team" (#11109).

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

* Call a lapsed Claude access token paused, not signed out (#8093)

* Refresh Claude usage after the clock moves backwards (#9956)

* Bound unreadable Claude transcript warnings (#12414)

* Count Claude usage from opencode v2 sessions (#13894)

* Reload agent usage records when an inotify watch fails to rearm (#10067)

* Reload agent usage records after each update run instead of on a timer

Rather than #10067's two-minute timer per record, reload every record when
the omarchy-agent-usage-update process exits, the moment its files can have
been replaced. A reload that finds a file unchanged keeps its record, so the
panel isn't stirred up by identical data. The grep test now runs the QML
functions.

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

* Show the agent status when the trouble line has no help text (#8497)

* Clear stale agent login guidance after a successful probe (#8892)

* Clear the Grok login hint after a successful probe

#8892 cleared the default login hint after a successful probe in the
Claude and Codex collectors; Grok's collector had the same stale hint.

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

* Read Fireworks credentials from pi's auth.json (#7455)

The Fireworks collector skipped pi, Omarchy's default agent, when
walking its credential ladder, so a machine signed in to Fireworks only
through pi (/login fireworks) never showed the tab. Insert the key pi
stores in $PI_CODING_AGENT_DIR/auth.json (default ~/.pi/agent) between
the firectl auth.ini and the opencode fallback.

pi keys can be literals, $ENV_VAR/${ENV_VAR} references, or !command
shell lookups. The collector resolves the first two; command lookups
stay pi-only and are skipped rather than sent to the API verbatim.

* Call a lapsed Grok access token paused, not signed out

Grok's access token lives six hours and Grok mints a new one from its
refresh token whenever it starts, so a lapsed one is routine. Reporting
it as an expired sign-in made the panel offer Sign-in required several
times a day, sending people through grok login for nothing. With a
refresh token present it now reads as paused, like Claude's.

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

* Keep showing Grok's last limits while it sits idle

While Grok hasn't run, nothing on the machine has spent its allowance,
so with a refresh token on hand the last numbers still stand: they show
as current rather than dimmed under a status line. A weekly window that
reset in the meantime starts over at 0%, a whole number of weeks on.

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

* Ask for a Grok sign-in once its refresh token is past 30 days

A refresh token older than Grok's 30-day sign-in can't renew anything,
so the panel offers Sign-in required again instead of showing the last
limits as current. With nothing cached yet it says to start Grok, rather
than showing an empty section without a word.

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

* Check both ends of what the Claude index read before resuming a transcript

A transcript rewritten in place could grow and change only after its
first kilobytes, and the index took it for an append. It now compares
the last kilobytes before the resume point too.

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

* Simplify the agent usage collectors

- Codex: pass the forced-scan choice down instead of a module global, make
  the per-file reader's cache arguments required, shrink the cache record
  check, and drop guards for shapes that can't occur: an empty launcher
  path, realpath raising, mise itself being a lazy launcher, multi-line
  `mise which` output, and probing without a temp file for stderr.
- Claude: decide an append by the digest of both ends of what was read
  alone; the inode and mtime checks it made redundant are gone.
- Snapshot: the device id falls back to the hostname, which always exists.

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

* Share fixture setup in the agent usage scanner tests

Every fixture home lives under one scratch directory with a single cleanup
trap, instead of a trap rewritten with a longer list for each new home, and
the Codex test builds its signed-in homes with one helper.

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

* Treat a replaced Claude transcript as new even when its ends match

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

* Probe Codex without its error text when there's no temporary space

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

---------

Co-authored-by: tossbaws <17258053+tossbaws@users.noreply.github.com>
Co-authored-by: surim0n <suritech@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: anonwurcod <anonwurcod@proton.me>
Co-authored-by: Kevin Rajan <7121943+kvnloo@users.noreply.github.com>
Co-authored-by: Nate Ashby <nate.ashby11@gmail.com>
Co-authored-by: Aris Gysel <aris.gysel@me.com>
Co-authored-by: Brams <76213579+Brams-s@users.noreply.github.com>
Co-authored-by: This_Is_NPC <gabrielfollone27@gmail.com>
Co-authored-by: sanjyay <102979855+sanjyay@users.noreply.github.com>
Co-authored-by: PapistProtocol <12738904+PapistProtocol@users.noreply.github.com>
Co-authored-by: steez <stevedimakos97@gmail.com>
Co-authored-by: GPT-6 Astra <noreply@openai.com>
Co-authored-by: Ryan Yogan <ryanyogan@gmail.com>
Co-authored-by: Oli Denton <41393837+omdenton@users.noreply.github.com>
Co-authored-by: Igor Kramar <i@ikramar.ru>
Co-authored-by: Martin Eidensten <martin@meibe.se>
Co-authored-by: Romain Perron <rdj.perron@gmail.com>
Co-authored-by: Omarchy Contributor <contributor@users.noreply.github.com>
Co-authored-by: manuaudio <manu@arimaka.com>
Co-authored-by: Tyler South <tsouth2@gmail.com>
Co-authored-by: whathek <Hek846@users.noreply.github.com>
Co-authored-by: Ty Richards <me@tyrichards.com>
2026-10-02 22:03:07 -04:00
821ae58905 Reorder the agents in the panel, count Grok's tokens, and install Grok through mise (#14004)
* Let the agents in the panel be put in any order

Drag an agent by its mark to move its section; the header it will land
on lights up, and the move happens on release. Each agent's header is
now a keyboard stop with its own highlight, and Ctrl+Up/Down moves the
agent the cursor is in. The order is kept in agents/order.json beside
the usage records. The key catcher turns Ctrl+Up/Down into a reorder
only for panels that opt in.

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

* Light only an agent's mark when the cursor or a drag is on it

The mark is the handle the agent moves by, so the keyboard cursor and
the drop spot while dragging box it alone rather than the header line.

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

* Keep a lit agent mark's box from being clipped at the panel's edge

The box overhangs the content's left edge, which the scrolling area
clipped. The scrolling area now reaches a little into the panel's
padding with the content shifted back, so nothing moves and the box
draws whole.

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

* Reuse a Grok session scan only on the day it was made

A limits-only refresh just after midnight reused a scan from the evening
before, which counted yesterday's sessions as today's.

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

* Keep Ctrl+Up/Down from moving an agent when the cursor is outside one

The hero's buttons and the starter tiles carry indices too, and the
lookup read them as accounts, so with several accounts Ctrl+Up/Down on
one of them moved an unrelated agent.

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

* Report a failed mise update even after the Grok upkeep runs

The Grok block ran after `mise up` and its last command set the script's
status, so a failed tool update could read as a success to callers that
warn about it. The update's own status is now the script's.

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

* Unpack a Grok update into ~/.grok whatever GROK_HOME says

The upkeep replaces ~/.grok's link, but the npm launcher unpacks into
GROK_HOME when it's set, so with a custom home the link never came back
at the new release and the old one was restored.

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

* Hold off reordering agents while an account name is being edited

Ctrl+Up/Down reached the key catcher during an inline rename, and moving
the agent rebuilt its section, dropping the unfinished name.

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

* Install Grok through mise's first-party package

The npm launcher keeps an old ~/.grok/bin binary because mise skips its
postinstall. Use mise's grok tool, and drop the npm tool so its shim
does not stay ahead of the stub.

* Drop the npm Grok workarounds now that mise installs Grok itself

With Grok installed through mise's first-party package, the CLI is the
binary mise manages, so the update upkeep that repointed ~/.grok/bin and
the shared bin directory for added Grok accounts have nothing left to do.
omarchy-update-mise is back to running mise up and nothing else, and
adding a first Grok account installs the same package.

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

* Count Grok's tokens and prompts from its usage ledger

Each Grok session keeps a usage.json, the ledger `grok usage` prints, with
every finished turn's end time and tokens by model. The collector now
reads it for tokens today, by day for the last week, and by model, with
cached input kept apart, and counts today's prompts by the turns that
ended today. Found in #12352's research.

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

* Count cache writes in Grok's daily token totals

Grok's totalTokens leaves cache writes out while the per-model buckets
count them, so a turn with cache writes added less to its day than to
its model. The day's total is now the sum of those same buckets.

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

* Remove the npm launcher's old Grok binaries when moving to mise's Grok

Omarchy's npm wrapper ran the binary the launcher unpacked into
~/.grok/bin, where x.ai's installer also puts a copy with a PATH entry
ahead of mise. Once the wrapper is replaced, a binary left there would
keep shadowing the mise tool, so the migration removes it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jesse Miller <jmiller@jmiller.com>
2026-10-02 03:34:05 +02:00
David Heinemeier HanssonandClaude Opus 5.5 f45461a38f Bring Grok up to par with Claude and Codex in the agents panel (#13992)
* Let Grok updates through mise take effect

Grok's npm launcher runs ~/.grok/bin/grok whenever it exists, and the
install script that repoints it at a new release doesn't run under mise,
so every machine kept running the release it first unpacked. Updating
mise tools now drops a link to an older release and the old binary, and
lets the launcher unpack the installed one.

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

* Keep several Grok accounts, the way Claude and Codex do

Grok honors GROK_HOME, so an added Grok account gets its own home holding
only its login and settings cache, with the CLI binary, sessions, skills,
plugins, memory, and config linked back to ~/.grok. The account commands,
the add flow (a second account signs in through a private window), the
launcher, and a grok shell function all take it like the others, and its
identity and plan come from the files Grok writes at login.

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

* Show Grok's plan and credits in the agents panel

A grok collector reads each account's plan from the settings Grok caches
and its credits from the endpoint behind Grok's own /usage view, so Grok
gets a section, per-account limits, and autoswitch like Claude and Codex.
A signed-in agent with a plan now shows before its first numbers, so a
lapsed sign-in has somewhere to say so. Grok's mark joins the assets, and
with every agent able to take another account the add screen no longer
needs to dim one.

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

* Document Grok accounts and limits alongside Claude and Codex

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

* Read Grok's credits the way xAI actually answers

The answer nests under config, names a weekly period with its end, and
as protobuf JSON leaves the usage percentage out while it's zero. The
collector now reads that shape, so a fresh week shows as 0% until it
resets rather than as nothing known.

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

* Count Grok's prompts and sessions from its session summaries

Grok keeps a summary beside each session with when it was last active
and how many prompts it had, so its section and the hero's summary get
today's prompts and sessions and its active days. It records no token
counts there, so none are claimed.

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

* Give each Grok limits cache write its own temporary file

Two overlapping collector runs wrote the same cache through one .tmp
path, so one rename could leave the other's failing and its record not
updated.

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

* Read a single Grok account from GROK_HOME when it's set

The launcher and the CLI honor GROK_HOME, but the collector always read
~/.grok, so a Grok signed in only in a custom home showed nothing. With
one account, the collector now reads the home the CLI would; with
several registered, the primary stays ~/.grok.

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

* Drop guesses the real Grok credits answer made unnecessary

The collector carried fallbacks for fields and shapes guessed from the
binary before xAI's actual answer was seen, a numeric timestamp branch
nothing writes, the panel's default for prompt stats, and guards that an
empty sign-in already covers.

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

* Keep the working Grok release until its update is in place

The update dropped the link and older binaries before the new release was
unpacked and ignored a failed unpack, which could leave every Grok
account without a CLI. The old release now stays until the new one is
linked, and its link comes back if it never is.

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

* Count only Grok sessions for today, not prompts

A session's summary holds all its prompts and only when it was last
active, so a resumed session put its whole history on today. Today now
counts the sessions active in it; prompts stay an all-time total.

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

* Reuse the Grok session scan on a limits-only refresh

Opening the panel and near-limit checks only need fresh limits, so they
reuse a scan up to 15 minutes old, as the Codex collector does; --force
still rescans.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:37:56 +02:00
David Heinemeier HanssonandClaude Opus 5.5 c05d90196f Switch between several Claude and Codex subscriptions, and build apps the Omarchy way (#13770)
* Plan multiple Claude and Codex accounts with manual or automatic switching

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

* Keep several Claude and Codex accounts and start new sessions as the active one

Each added account gets its own home holding only its login, with history,
settings and skills linked back to ~/.claude or ~/.codex so --continue works
across a switch. Accounts are added through the CLI's own login, and cx, cy,
plain claude/codex and omarchy-agent all start as the active account unless
CLAUDE_CONFIG_DIR or CODEX_HOME is already set.

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

* Report limits for every Claude and Codex account in the usage records

Each registered account is probed with its own sign-in and cached on its own,
and a parked account whose sign-in lapsed keeps its last-known numbers marked
stale. The record's top-level limits keep describing the active account.

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

* Switch accounts automatically near a limit, or notify with a one-click switch

After each usage update, an active account at or over its provider's
threshold (95% by default) moves new sessions to the account with the most
headroom in auto mode, or offers that switch as a notification in manual mode.
It never flaps back to an account that just reset, and says once when every
account is over.

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

* Show every subscription account's limits in the agents panel

With several Claude or Codex accounts, the limits become one card per account
with the active one badged. Pick a card with its number and press Enter (or
click Use) to move new sessions to it, press a to add an account, and m to
toggle automatic switching. Limits refresh every minute while an active
account is above 80%.

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

* Add Setup > Agent Accounts to list, switch and add Claude and Codex accounts

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

* Document switching between several Claude and Codex subscriptions

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

* Count a rested parked account as available, and pin Main to its own home

A parked Claude account whose windows all reset now reads as 0% instead of
unknown, so switching can pick it. Main's Codex limits come from ~/.codex even
when CODEX_HOME is set, and duplicate logins are checked against who each home
is signed in as now.

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

* Key limits caches by subscription and report when an account truly frees up

An added account's limits cache follows its account id, so a new account
reusing a removed one's label never inherits its allowance. The all-accounts
notice now names when an account's blocking windows have all reset, not the
earliest reset of any window.

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

* Keep account ids clear of routing keywords and refresh the panel after removal

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

* Share Codex plugins and hooks across accounts, and key Claude caches by current sign-in

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

* Drop the key hint from the Add account button

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

* Sign new agent accounts in through a private browser window

The main browser is almost certainly signed in to the account you already
have, and the login would silently reuse it. Both CLIs open their login page
through $BROWSER, so it now points at omarchy-launch-browser --private.

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

* Let agent account commands default to your default agent's provider

With Claude or Codex as the default agent, the provider can be left out:
omarchy agent account use work, and primary names the primary account.

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

* Drop the primary alias, which shadowed an account named Primary

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

* Replace the auto switch button with a small Notify / Autoswitch toggle

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

* Add accounts from a small + beside the switch toggle

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

* Fix the punctuation of the switch toggle's README line

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

* Drop the border around the add account +

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

* Rename Claude and Codex accounts without moving their homes

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

* Lay out agent accounts on accent rails instead of boxed cards

The active account gets an accent rail and the others a quiet one, each
window is a single compact line, and the switch toggle, add, and Use are text.
Clicking an account's name renames it in place. A window without a reset time
no longer leaves an empty line, and last-known numbers are only red when the
sign-in needs attention.

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

* Read Codex limits without waiting on account/read

Codex 0.158's app-server can leave account/read unanswered, and asking it
first lost the limits whenever it did, leaving the agents panel showing
"Codex limits unavailable". The limits name the plan themselves, so they're
asked first and account/read is only a short fallback when they don't.

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

* Switch agent providers from their marks in the panel header

The row of provider buttons gives way to a small mark per provider in the
hero's corner, the selected one at full strength, with the add account +
beside them in place of the + by the switch toggle and the full-width Add
account button.

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

* Keep the agents panel's scrollbar off its contents

The panel's content narrows to leave the scrollbar its own strip whenever it
scrolls, and the add account + leads the provider marks instead of sitting at
the very edge.

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

* Add Claude, Codex, or Grok subscriptions from one Add Account menu

The first account for a provider installs its CLI if needed and signs in to
the CLI's own home through the normal browser; only additional accounts get a
home of their own and a private window. Grok signs in its first account.

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

* Reveal Autoswitch beside Use instead of a Notify / Autoswitch row

The panel's + now opens the Add Account menu for any provider.

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

* Split agent accounts with plain separators instead of rails

ACTIVE already marks the account new sessions use.

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

* Only call Claude limits stale once they're old, and poll near a limit less

Anthropic rate-limits its usage endpoint, and polling two accounts every
minute near a limit got every re-check refused, so both accounts read
"Last known" with numbers a minute old. A refused check of numbers under
15 minutes old now counts as current, older ones say how old they are,
and near-limit polling is every three minutes.

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

* Show every agent's limits on one page under an Agents hero

The panel stops switching between one provider at a time and lists every
agent and account with its limits, dropping the tokens-by-day and by-model
charts. The hero carries the agents robot and rotates through what the token
counts add up to: tokens this week and today, the most used model, the
busiest day, and today's prompts and sessions. Middle-clicking the bar icon
refreshes, since there's no provider left to advance to.

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

* Report Codex's free rate-limit resets in its usage record

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

* Give the agents panel more room, and show Codex's free resets

Limit lines are a notch larger with more space between them, each account's
limits sit a clear step below its name, and sections breathe. Codex has one
limit on Pro, so its section now also says how many free full resets are
waiting and when the next one lapses.

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

* Mark Claude's Fable limit on the Weekly meter instead of its own row

A model-scoped allowance on the same clock as a base window is drawn as a
tick on that window's meter and named in the row's tooltip.

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

* Let omarchy-default-agent set the default without launching it

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

* Let the agents panel drive adding an account without a terminal

--check says whether adding one now would be each provider's first sign-in
or an additional account, and --events reports progress as tagged lines
alongside the CLI's own output, notifies with the result, and cleans up the
login and its scratch home when cancelled.

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

* Add subscriptions and pick the default agent right in the agents panel

The + is a proper accent button, and it swaps the list for a picker of
Claude, Codex, and Grok that signs in without a terminal: name a further
account, then follow the sign-in with its status, Grok's confirmation code, a
field for Claude's pasted code, and a link to reopen the page. A dropdown at
the bottom sets the default agent without launching it.

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

* Draw the Fable tick in its meter's own color

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

* Revert "Let omarchy-default-agent set the default without launching it"

This reverts commit da96261f7d26bb76774cbdc1cb9e0abde92bb841.

* Make the first agent signed in the default when none is picked

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

* Keep the agents panel in the bar, with setup, starters, and adding in it

A machine with no agent opens the panel on setting one up. Once set up, the
list ends with starter prompts for a new theme, plugin, or app, and a quiet
Add a subscription. The panel no longer sets the default agent, and the hero
drops its add button.

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

* Offer the starter prompts as tiles, and adding as a row beneath them

Theme, Plugin, and App each get a tile with its glyph, and Add a
subscription a matching row with the + in a tinted square. The panel is
allowed to grow tall enough to show it all without scrolling.

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

* Put the tinted add button in the agents panel's hero corner

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

* Show when a limit resets in small type under its meter

The percentage keeps the right edge to itself, so the meter runs longer.

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

* Revert "Show when a limit resets in small type under its meter"

This reverts commit 108d9de59da1af12cddbf232b0b5333ad8994c64.

* Put each account's plan beside its name, and its email in a tooltip

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

* Stack each limit's percentage over its time left, so the meter runs longer

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

* Show only each limit's time left, with the exact percentage on hover

The meter already says how full a window is. Dropping the percentage beside
it leaves one calm figure per row, and the row's tooltip carries the number.

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

* Sign an existing account in again with omarchy-agent-account-add --reauth

It signs in where the account already lives: the CLI's own home (:primary)
through the normal browser, an added one through a private window. The usage
records now say which account is the primary.

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

* Offer Sign-in required in the agents panel instead of how old the numbers are

A lapsed sign-in shows as a link that signs that account in again right in
the panel. The "as of" and "last known" notes are gone; trouble that isn't
about signing in still shows as text.

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

* Keep the separator dot out of the Sign-in required link

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

* Drop the Setup > Agent Accounts menu and the stale plan

The agents panel now lists, switches, and adds accounts, so the menu's
duplicate of it goes, and the plan written before the design settled no
longer describes it.

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

* Install the compiler and Qt pieces for building Omarchy-style apps

base-devel plus qt6-base, qt6-declarative, qt6-multimedia, and qt6-wayland,
which is what Hype, Monologue, and Omacut build with through qmake6 and make.
Qt was only there as a dependency of those apps, and the compiler not at all.

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

* Add an omarchy-app agent skill for building apps the Omarchy way

It teaches how Hype, Monologue, and Omacut are built: C++ and Qt Quick in
one flat project, qmake6 and make into a single binary, a theme that follows
Omarchy's accent live, portal dialogs, keyboard-first conventions, Qt Test
offscreen, and a PKGBUILD that puts the app in the launcher, with starter
files that build and pass their tests as written. The agents panel's App
starter uses it.

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

* Link the omarchy-app skill on existing installs

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

* Let Cancel stop a waiting login, and list a lone account's limits

Bash holds a trap until the foreground command finishes, so a login waiting
on the browser outlived Cancel. It now runs behind an interruptible wait.
With one account the usage record keeps its limits at the top level, which
omarchy agent account list now reads for the primary.

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

* Re-read identity even when signed out, and count Grok as set up

A home whose login is gone no longer keeps the identity the registry saved,
so it reads as signed out and can be added again. The agents panel leaves its
setup screen once any agent is signed in, not only once one has usage to
show, since Grok has no usage collector.

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

* Save each account's current identity before collecting usage

A home signed in to someone else since it was added kept the old email in
the registry, which the usage records name accounts by. The usage update now
refreshes the registry from each home first.

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

* Offer Claude's paste field from the start, and keep providers with accounts

Claude's login prompts for a pasted code without a newline, so the panel's
line reader never saw it; the field is simply there for Claude sign-ins. A
provider with accounts stays listed even when the active one's limits are
unavailable, so its other accounts can still be switched to.

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

* Never autoswitch to a signed-out account, and report Codex's missing sign-in

An account nobody is signed in to is left out of switching, however much
room its cached numbers show. Codex answers a home without a login with an
error, which now reads as Waiting for auth, so the panel offers Sign-in
required for Codex accounts too.

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

* Link shared files before they exist, and only call every account over when it is

A shared file the primary home didn't have yet was left unlinked, so an
added account made its own copy and the two diverged for good; it's linked
up front now, and written in the primary home by whichever account writes it
first. The all-accounts-over notice waits until every account's limits are
actually known.

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

* Make the agent starter tiles compact and call the section Make something cool

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

* Hold the agents hero line until its next fade

Every usage record that landed rebuilt the summary phrases, and the hero
indexed that live list, so opening the panel could swap the line several
times between fades. It now keeps what it shows until the timed swap.

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

* Start the default agent from the agents panel hero

A console button beside the add button runs the same launcher as the
right click: the default agent, or the picker when none is set. It hides
while adding a subscription, where the add button becomes the way back.

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

* Put the add button before the agent launcher in the hero corner

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

* Bring keyboard navigation back to the agents panel

The one-page redesign dropped left and right with the per-agent pages,
leaving the arrows only to scroll. They now walk everything that does
something, row by row: the hero's add and launch buttons, each
switchable account, and the starter tiles. Enter acts on the cursor,
the cursor scrolls into view, and hovering moves the same cursor so only
one thing is lit. Number keys still jump to an account.

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

* Let adding an account take over the agents hero

While adding, the hero's line reads "Add an account" in place of the
rotating summary, and the X in its corner is the only way back, so the
add view drops its own title and Back link. Each agent to add is just its
mark and name; one that can't be added is dimmed and says why on hover.
The arrows walk that list too, and Enter picks one.

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

* Show the agents to add as large marks, three across

Each agent is a large mark over its name with no box around it, in one
row the arrows move along. The chosen one turns accent and grows a touch.
The reason an agent can't be added is shortened to fit inside the panel.

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

* Move between Autoswitch and Use on an account with the arrows

An account that isn't active is now two stops, Autoswitch then Use, so
left and right move between them and Enter acts on the one that's lit.

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

* Land on Use when moving up or down onto an account

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

* Hide Use on an autoswitching account until you're on its line

With Autoswitch on, the line shows only Autoswitch. Use appears when the
line is hovered anywhere or the keyboard is on it.

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

* Start adding an account with the first agent focused

Opening the add screen puts the keyboard cursor on the first agent, so
Enter picks it straight away. A focused agent lights up even before the
check says whether it can be added.

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

* Drop the Sign in link under the account name field

Enter in the field already starts the sign-in.

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

* Key the primary Claude account's limits cache by its subscription

The primary home always used claude-limits.json, so signing it in to
another subscription could carry the old one's numbers over when the
first probe failed, and autoswitch would act on them. Once the home says
who it's signed in as, its cache is keyed by that like every other
account's.

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

* Leave Qt Multimedia out of the base packages again

Quattro dropped it once the shell no longer needed it, and the app skill
already has an app that plays audio or video add it and list it in its
own depends. The compiler and the rest of Qt stay.

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

* Render the agents panel's dynamic text as plain text

Sign-in status, help text, and provider names come from outside the
shell, so none of them should be read as markup.

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

* Keep an account's plan and trouble clear of its Use and Autoswitch links

The details beside an account's name grew to their full width, so a long
plan or warning could run under the links on the right. They now shorten
with an ellipsis instead, and Sign-in required keeps its whole width.

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

* Let primary name the first account, as the manual says

`omarchy agent account rename primary Hey` was documented but failed,
since the primary account's id is main and lookups took exact ids only.
primary now reaches the account marked primary, whatever it's been
renamed to, and no new account can take primary as its id.

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

* Install everything an Omarchy app needs by default

The app skill builds with Qt Multimedia, SVG icons, and ffmpeg as well as
the compiler and the rest of Qt, so qt6-multimedia, qt6-svg, and ffmpeg
join the base packages and the migration. The shell still plays no video
through Qt Multimedia, which is what its test now checks.

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

* Dim agent limits kept from an earlier check, with their age on hover

When a Claude probe fails, the last numbers carry on and looked just like
fresh ones. Those meters now dim, and their tooltip says how old they
are. The record carries limitsStale and limitsFetchedAt for this.

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

* Give the app skill templates for every file its build needs

The templates named src/backend.{h,cpp} and a test file without showing
them, so each app had to invent its own. They're now templated, with a
starter icon, a note that LICENSE and the icon must exist for package(),
and qt6-wayland in the PKGBUILD's depends. Scaffolded from the templates
alone, the app builds and its tests pass.

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

* Start the agents panel cursor over when the agent list comes or goes

Right after the shell starts, the panel can briefly look like a first
setup and focus the first agent to add. When the records land the rows
change under the cursor, which then lit an account nobody had picked.

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

* Drop dead hover and limit checks from the agents panel

The tiles and hero buttons fell back on their own hover when there were
no keyboard rows, but the hero always has one, so hover only ever moves
the cursor. Stale meters only exist when there are limits, so neither
the panel nor the record needs to check for some.

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

* Drop the a hotkey for adding an agent subscription

The + in the hero is the way in, by mouse or by arrowing to it. The
plugin README also catches up with the panel: the launcher, dimmed stale
limits, the new add screen, and the arrows walking a cursor rather than
scrolling.

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

* List a command family that sits under a flags-only command

`omarchy agent account` reached omarchy-agent, which takes only flags,
and failed on the word instead of listing the account commands. When the
command matched so far takes only flags and the next word names visible
commands, the router now lists them. `omarchy update aur` likewise lists
the update aur commands rather than running a full update.

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

* Refuse to remove an agent account a running session uses

Removing an account deleted its home at once, pulling the login out from
under any session started in it, though running sessions are never meant
to be touched. It now says to quit that session first.

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

* Prefer an account checked just now when switching near a limit

Autoswitch weighed numbers kept from an earlier check like fresh ones.
A parked account's sign-in lapses within hours, so a stale one stays a
candidate, but an account checked just now wins over it.

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

* Leave a user's own omarchy-app skill in place when linking ours

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

* Reach Sign-in required with the keyboard in the agents panel

Both the link on a lapsed account and the one on a single-account agent
are now stops in the cursor's walk, and Enter signs in again. A picked
link scrolls into view.

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

* Bring the manual's agent accounts paragraph up to date

Near-limit checks run every three minutes, not every minute, and
Autoswitch now appears on hovering an account's line and takes Use's
place while it's on.

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

* Keep the agents panel cursor on things you can act on

The active account was a stop with nothing to do, so the cursor seemed to
vanish there. It now only stops on it to sign in again. The hero's
buttons also show the cursor plainly, with an accent border and a deeper
tint instead of a shade's difference.

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

* Never leave an added agent account's login outside the registry

Registering moved the login into its home before carrying settings over
and saving the registry, so a failure in either stranded a home holding
a sign-in that the account commands couldn't see. Settings are now
carried while the login is still pending, and a failed save moves the
home back there for the add command to clean up.

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

* Put room before freshness when picking an account to switch to

Preferring accounts checked just now outright could pick a fresh one at
94% over a stale one at 10%. An account within 15 points of the
threshold now counts as near its limit however fresh, so accounts with
room come first and freshness only decides among them.

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

* Check agent limits more often relative to the switch threshold

Faster checks started at a fixed 80%, so a threshold set lower could be
crossed and wait out the 15-minute interval. They now start 15 points
below the threshold, which is still 80% at the default 95%.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:36:22 +02:00
3cebdc3412 End a running idle cycle when both timeouts are set to 0
With both at 0 the monitor is disabled and handleIdleChanged returns early, so a cycle already running when shell.json changed never cancels: omarchy-system-wake never runs, and if the screensaver never opened a window nothing else ends it. Cancel it the way turning on stay-awake does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
2026-10-01 12:30:46 +02:00
3839d0b4e0 Keep a pending idle action on its deadline when the timings change
With 0 meaning off, setting the screensaver to 0 while the screensaver is up moves the first idle deadline to the lock's, which turns the pending lock's bound interval to 0 and locks at once, minutes early. Set each timer's interval when the cycle starts, so a change to shell.json takes effect from the next cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
2026-10-01 11:45:45 +02:00