* Rebind ghost Copy URL shortcut registrations to the pinned id
Chromium never hands a suggested shortcut to one extension while
another — even a long-gone one — still holds the registration. Profiles
that first loaded Copy URL before its id was pinned registered
Alt+Shift+L under an id derived from the extension's load path at the
time, so the pinned extension never receives the shortcut and the
keypress does nothing (#6816).
The quattro upgrade tried to repair this against one hardcoded
path-derived id, which only ever matched a single home directory. The
historical ids are unknowable in general — they hash long-gone absolute
paths through whatever symlinks existed then — but the registration
itself names the command, so a migration now rebinds any copy-url
command that points away from the pinned id, unless that id belongs to
an extension that is actually installed or the pinned extension already
holds a binding of its own.
Browsers rewrite Preferences on exit, which reverts any repair made
while one runs, so the migration asks for this user's browser windows to
be closed first — failing and staying pending when there is no terminal
to ask in or the prompt is declined. The backup a repair leaves behind
marks it as attempted but unverified: until a browser-free run confirms
the registration stayed repaired, the migration keeps itself pending
rather than trusting a disk state an open browser may still overwrite.
The upgrade-time repair is dropped: the upgrade already runs migrations,
so the migration is the single implementation.
Fixes#6816
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Pin the WhatsApp Slim extension id
Keyless unpacked extensions get path-derived ids, which go stale if the
load path or packaging ever changes — the same class of bug that broke
the Copy URL shortcut for pre-package installs. Pin the id with a
manifest key like the other bundled extensions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
* Make menu overflow visible with a half-row peek and edge fades
Menus used to cut off clean at ten rows, so anything below the fold was
undiscoverable. Now the list sizes to what fits within 60% of the screen
and always ends mid-row when items overflow, with scroll-position-driven
fades at both edges. Keyboard navigation keeps the next hidden row peeking
past the cursor so the fold affordance travels with the selection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Collapse WhatsApp Web to a Signal-style avatar rail in slim windows
Add a WhatsApp Slim Chromium extension that injects CSS into
web.whatsapp.com, collapsing the chat list to a 90px avatar rail with
floating unread badges when the window is narrower than 1100px. Wide
windows keep the stock layout.
Loaded through the existing --load-extension list in chromium-flags.conf,
with a migration to append it for existing users.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The Copy URL extension was intermittently failing to copy anything, and
its notification looked oversized with no icon. Several distinct problems:
- The MV3 service worker was pinned to stale cached code. Chromium caches
the worker script for a --load-extension command-line extension and does
NOT re-register it when the file changes in place — not on restart, not on
a manifest version bump, not even after a clean shutdown. So updated code
never took effect: the old worker kept calling chrome.scripting.executeScript,
which throws once the scripting permission is gone, and the copy silently
failed. Renaming the worker to a versioned filename (background-2.js) is a
new script URL, which forces a fresh registration for everyone — new
installs and existing installs alike. Rename it again on any future worker
change (see the note at the top of background-2.js).
- Clipboard writes now go through an offscreen document (MV3's sanctioned
path) using a textarea + execCommand('copy'), replacing executeScript +
navigator.clipboard. This works on chrome:// pages and needs no scripting
permission. execCommand is used deliberately: navigator.clipboard.writeText
rejects in an unfocused offscreen document.
- Add a toolbar action (chrome.action.onClicked) so the extension is
clickable, not keyboard-only — it was greyed out in the extensions menu
with nothing to invoke.
- Slim the "URL copied" notification: lead the summary with a glyph so the
omarchy notification shell collapses it to a single-line toast with an
icon, instead of an oversized card with a blank icon slot.
- Guard the Quattro shortcut-repair migration: it edits Chromium Preferences
to move the Alt+Shift+L binding to the new extension id, but a running
browser rewrites Preferences on exit and reverts the edit. Prompt (via gum)
to close the browser first, only when a browser is running and there is
actually a stale binding to repair.
Download the current page's video with yt-dlp via Alt+Shift+D or a click
on the toolbar icon. A native-messaging host runs the download, shows live
progress on the Quickshell OSD, and posts a clickable "Download complete"
toast that opens the file in mpv.
- Extension: pinned key for a stable id, green download-video icon,
keyboard command + toolbar action (reads the active tab URL).
- Native host (omarchy-chromium-ytdlp-host): verifies the URL with
yt-dlp --simulate (else "No video found"), streams progress to the OSD
(time-throttled to ~4/s), saves to ~/Videos, opens mpv on click.
- Installer (omarchy-install-chromium-ytdlp) writes the native-messaging
manifest into installed Chromium/Chrome/Brave/Edge profiles; wired into
browser install and chromium refresh, with a migration for existing users.
- omarchy-osd: add -d/--duration so the OSD can persist during a download.
- Add yt-dlp to base packages, load the extension via --load-extension,
and document the Alt+Shift+D binding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Copy current URL to clipboard with browser extension
In Omarchy web apps it's impossible to get the curent URL to share it in
another applications. This commit adds a browser extension which is
included by default via the chromium-flags.conf config. With this
extension you can get the current URL via a keyboard shortcut.
The default shortcut for getting the current URL is `Ctrl+Shift+L`, this
shortcut can be changed via this page: chrome://extensions/shortcuts.
* Change the shortcut to Alt+Shift+L
In my testing and research I found out that not all shortcuts
are available as suggested_key. We still want to use L because
that's easy to remember because we associate it with the location bar.
* Improve notification language
* Move it to just Alt + L since that's not used
Better mirrors Ctrl + L as well
* Move this under default/chromium
* Fix symlink
* It didn't like Alt+L alone after all
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>