* Make WhatsApp Web follow your Omarchy light/dark theme
WhatsApp Web's "System default" theme follows prefers-color-scheme and
repaints live, so a small theme bridge is enough to make it track the active
Omarchy theme with no reload and no WhatsApp-specific CSS.
- omarchy-chromium-theme-host: push-only native messaging host that reads the
active theme and emits it on connect and on every theme-set. Unlike copy-url/
yt-dlp (one-shot), it stays connected and pushes, since theme-following needs
the page to learn about changes while it is running.
- omarchy-chromium-theme-refresh: SIGUSR1s the running host(s); called from
omarchy-theme-set's post_theme_commands.
- whatsapp-theme extension: decides dark vs. light from the theme background's
WCAG luminance and drives a prefers-color-scheme shim, so WhatsApp's own
theme does the repaint.
Wired like copy-url/yt-dlp and whatsapp-slim: bundled under
default/chromium/extensions, added to --load-extension, host manifest
registered from the fresh-install/refresh/browser-install paths, existing users
covered by a migration.
The host is named com.omarchy.theme (a generic theme bridge) rather than
WhatsApp-specific, so other bundled web-app extensions can follow the theme by
connecting to it and adding their id to the host manifest's allowed_origins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address review on the WhatsApp theme bridge
Light/dark was decided by weighting raw sRGB bytes, which the comment above
it already described as WCAG relative luminance. sRGB is gamma-encoded, so
the weights only mean anything once each channel is linearized — the two
steps the shell already does in Panel.qml. Every shipped theme classifies the
same either way; a mid-tone custom background does not (#808080 reads 0.502
unlinearized and 0.216 linearized).
Drop the `tabs` permission. The WhatsApp host permission is what lets
tabs.query filter by url and what populates tab urls in onUpdated, so `tabs`
only widened this to every tab's url and title. Tabs without permission
arrive with url unset and fall out on the existing guard.
Give the two new bin commands their metadata directives. Without a summary
they failed test/cli's command metadata check.
Cover all three: the classifier over unambiguous and mid-tone backgrounds,
and the manifest for the permission it should no longer ask for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Scope color-scheme listeners to their query and test the real host
Registrations all shared one Set keyed only by callback, so an app that gave
the same callback to both the dark and the light query and later detached one
detached the other too, leaving the query it still held deaf to theme
changes. Record the owning MediaQueryList and match on it. Adds native
dedupe behaviour while there: registering the same callback twice fired it
twice. addListener is a legacy alias of addEventListener("change"), so the
two share one registration space and either remover cancels either add —
which is also why useEvent had nothing left to select and is gone.
The refresh test signalled a synthetic sleeper carrying its own USR1 trap, so
it proved the refresh command sends a signal but would have stayed green
through any regression in the host's own trap, watchdog wait, or second
write. Drive the real host over a FIFO instead, count framed messages, and
assert the second one is a usable theme. Verified by neutering the host's
USR1 trap: the old test passed, this one fails.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Harden Chromium theme bridge
* Address Chromium theme bridge review
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Say it once and match mergeAppRows: app rows sort after all menu items,
not interleaved among them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Searching the menu for an installed app buried it: "brave" listed Setup >
Defaults > Browser, Install > Browser and Remove > Browser ahead of the
Brave app itself. All four are exact label matches scoring 0, so the
tiebreak falls to declaration order, and mergeAppRows appends app rows
after every static item.
Bias app rows ahead of menu entries that match equally well. The bias is
smaller than the gap between match tiers, so a menu entry that matches the
query better still sorts first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SCALE only applies to the focused monitor, which is easy to lose track
of with several displays. Show the focused monitor's name on the right
of the SCALE header (matching the BRIGHTNESS/TEXT SIZE value
convention), hidden when only one display is enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The popups used one Wayland layer surface per output sized to their content
(implicitWidth/Height bound to the toast column), so every time a toast was
added or removed the surface resized. During that resize the compositor
briefly scaled a stale buffer, stretching or squishing the cards mid-
transition -- worst with only one or two toasts, where each resize is a
larger relative change.
Make the surface full-screen and fixed, like the OSD overlay, so adding or
removing a toast only changes the content inside it and the surface never
resizes. Mask the input region to the toast column so the otherwise-
invisible full-screen overlay stays click-through.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
omarchy-launch-screensaver loops over the monitors, focusing each and
spawning a terminal on it -- but the spawn is asynchronous. Slow-starting
terminals mapped their windows after focus had already moved to the next
monitor, so every screensaver landed on the last-focused monitor and the
others got none (and the fullscreen rule applied inconsistently when two
windows mapped at once).
Listen on Hyprland's event socket and wait for each monitor's screensaver
window to open before moving focus to the next one, so there is exactly one
screensaver per monitor. The stream is opened before spawning so a
fast-mapping window can't be missed, with a read timeout as a safety net.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the other editors (VSCode/Zed/Cursor) by launching once install
finishes. Uses gtk-launch emacsclient so it opens a frame on the
emacs.service daemon that omarchy-install-emacs just started, rather
than spawning a second standalone instance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point the Emacs entry in the install-editor menu at a dedicated
omarchy-install-editor-emacs script, matching the pattern already used
for Helix, VSCode, and Zed. The script installs the omarchy-emacs AUR
package and runs its installer, which pulls in emacs-wayland, applies
the theme/font integration, and enables the user emacs.service.
This is the AUR-packaged approach dhh suggested on #4807 (superseding
the in-tree integration proposed there), now that the package exists and
is maintained on the AUR. End-user behavior is unchanged except that a
freshly installed Emacs now tracks the active Omarchy theme and font out
of the box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>