Repository navigation
OmaDock 4.14.0, released 2026-11-13 (main at 73cf94a, tag v4.14.0). Twenty-five commits since v4.13.2: four community contributions, four fixes found while running it on the desktop, and one attempted fix that was reverted before release because it made hover worse.
New
A pinned web app owns its window (#85, @LordMelkor). A web app pinned by name now matches its window through the desktop entry's StartupWMClass (chrome-mail.google.com__-Default for a Gmail pin), so the pin is the running window instead of a duplicate tile appearing beside it, and a window claimed that way is not also listed as a separate running app.
Unpinning removes the pin you are looking at (#82, @kingokeke). The pin toggle compares pins by app identity (isAppMatch) rather than the exact string, so an app whose desktop entry was renamed (hermes → com.nousresearch.hermes) unpins instead of gaining a second pin, and a list holding both spellings is cleaned up by one unpin.
The dock steps aside for the scratchpad (#80, @WhoIsCalebBrown). While a monitor shows the scratchpad, that monitor's dock leaves the screen and returns when the scratchpad closes. The state is one owner reading Hyprland's own monitor object rather than a stored event, and every unknown, missing or malformed reading answers "not open", which shows the dock: a lost event or a compositor reconnect cannot leave it hidden for the rest of the session.
Steam keeps its games apart (#80). The client and each game are separate identities by the game's number, so pinning a Steam shortcut no longer borrows the client's name or absorbs another game's window, and a running game with no desktop entry is named by its window title.
The native Apps shortcut reaches OmaDock (#83, @LordMelkor). A new apps IPC method toggles Omarchy's app grid independently of the dock button's menu/start/grid preference, falls back to the Omarchy menu where the grid is not installed, and the README documents the o.rebind("SUPER + ALT + SPACE", "Apps", "omarchy-shell omadock apps") override so the shortcut keeps working where OmaDock replaces the native dock.
Fixed
A folder tile is its icon again. With Show background off, a pinned folder drew the translucent contrast frame v4.13.0 gave it (28% fill, 45% rim), which reads as a stray rectangle around the artwork. The frame is gone from components/DockFolderItem.qml; the app-group tile keeps its own, because a 2×2 mini grid needs a plate.
A theme's artwork behind a symlinked directory now counts. The dock's icon index listed each size directory with find, which does not follow a link. WhiteSur-dark shares WhiteSur's apps/scalable through one (WhiteSur-dark/apps/scalable -> ../../WhiteSur/apps/scalable), so the active dark theme contributed none of its scalable app icons and names it does not ship at 22/16 px resolved from the inherited themes instead. find -L is the fix. Measured on a machine with WhiteSur-dark applied: the index resolves foot to /usr/share/icons/hicolor/scalable/apps/foot.svg with the old script and to ~/.local/share/icons/WhiteSur-dark/apps/scalable/foot.svg with this one, with 10 379 names either way. tests/unit/test_icon_index.py pins a symlinked size directory and fails against the old script.
A theme that shares its artwork through a symlink is listed and applicable. The icon-theme helper decides a theme is installed and complete by finding one real file inside it, and that find did not follow a symlinked size directory, so a theme whose only artwork is shared that way — an apps/scalable symlink beside its index.theme — was hidden from the picker's list and refused by apply although the rest of the desktop drew its icons. The completeness test follows links now, pinned by a fixture theme whose only artwork is behind one.
Picking an icon theme keeps the rest of kdeglobals. apply promises to preserve every key it does not own, but its rewrite dropped every Theme= line in the file and wrote one under [Icons], so a KDE configuration carrying Theme= in another section lost that setting the moment an icon theme was chosen. Only a Theme= inside [Icons] is replaced now, pinned against a fixture with Theme= in [General], [Icons], [KDE], [Colors:View] and [Icons-Scale].
Reverted before release: the wave-easing change
An intermediate commit during this cycle disabled each tile's Behavior on magnifyScale while the wave tracked the pointer, to remove the trailing ease one report described as "vibrating / laggy". On the real desktop it does the opposite: the row magnifies instantly under the pointer, the layout moves, hover re-enters, and the loop never settles. Measured on the live dock (autohide off): hover cost 299 jiffies / 3 s and the dock stopped answering IPC, and it stayed wedged after the pointer left the row; with those six lines removed the same hover costs 12 jiffies / 3 s, magnifies (9 423 px changed in the dock strip, against 18 px while wedged) and answers IPC, returning to 1 jiffy / 3 s after the pointer leaves. v4.13.2 reads 13 jiffies / 3 s on the same hover. The six Behaviors are back to v4.13.2's unconditional form, so the reported feel is not fixed in this release — it is left as it was rather than shipped worse.
Merge-time corrections
Scratchpad visibility has one owner. As merged in #80, three places decided the same thing (FloatingDockWatch, DockStateLogic.syncVisibility and the layer's own visible), with a per-monitor memory of the last activespecial event. Now FloatingDockWatch.scratchpadOpen is a pure read of DockFloating.scratchpadOpen(monitor), the event does exactly one thing (Hyprland.refreshMonitors()), and the layer's visible is the only consumer. DockStateLogic.syncVisibility is byte-identical to v4.13.2 and Dock.qml was not touched (2 378 lines, the repository's file-size ceiling).
The StartupWMClass rule is a named decision. buildEntries had a one-liner that wrote its cache inside a boolean expression and was inlined in three loops; it is now pinClaimsWindow(pin, runningId, startupClass) beside a cached startupClassFor(pin). Equivalence was measured, not assumed: 39 buildEntries scenarios (13 pin sets × 3 group sets, covering Steam game/icon/client ids, web-app classes, terminals, case-differing ids and duplicate pins) produced byte-identical JSON, and 1 083 pin/window/class comparisons agreed with the old rule. Unit cases pin the decision itself, with fixtures whose pin name shares no token with the class, so substring matching cannot make them pass by accident.
Two unrequested behaviour changes are not in this release. The hover-preview aspect retune and the tooltip lifecycle rework that came with #80 were reverted to v4.13.2 byte-for-byte (components/TooltipLife.qml, components/HoverTooltip.qml, components/WindowCardStack.qml, components/DockItem.qml), and DockModel.entryFor is back to its previous form. Previews, tooltips and group cells behave exactly as they did in v4.13.2.
The pin list moved to its own module. Rather than raise a size ceiling, the pin-list operations moved to a new root module DockPins.js (isPinned/togglePinned/reorderPinned/moveBefore), which decides no identity — its toggle takes DockModel.isAppMatch — so DockModel.js fell 1 326 → 1 284 lines and its ceiling follows it down. tests/unit/pins.test.mjs pins the list operations and the two id normalisers DockPins restates.
Verification
All of the following ran against this release's tree. The tag differs from the tested development commit only by the merge itself and this changelog.
| Gate | Result |
|---|---|
Node unit (node --test tests/unit/*.test.js tests/unit/*.test.mjs) |
321 pass, 0 fail |
Python (unittest discover -s tests/unit) |
154 tests, OK |
node --check on the 19 root modules CI gates |
clean (including DockPins.js, new in this release) |
qmllint (Dock, DockHost, components, settings, logic) |
0 errors, 2 743 warnings (the pre-existing import / plugin-dump noise) |
tests/static/structure-check.py |
PASSED — no file over its cap, logic modules pure |
tests/static/security-grep.py |
0 findings |
tests/static/i18n-en.py --check |
PASSED — 455 keys |
tests/static/shaders-in-sync.sh |
PASSED — committed .qsb match their sources |
tests/manifest-check.sh . |
PASSED |
omarchy plugin validate . |
0 |
tests/smoke-test.sh |
PASSED — layer mapped, IPC target registered, log clean |
Seventeen live suites were run: the probe suites start this release's own DockHost.qml in a second Quickshell instance over a disposable headless output, and the three that drive the running dock by design (indicators, layout, place-restore) ran against the live shell executing 4.14.0.
| Suite | Result | What it measured |
|---|---|---|
apps-menu.sh |
PASS | panel anchored over the apps button, its app list and 6 px gap, all four close paths |
autohide.sh |
PASS | a stale no-overlap answer is re-measured once ~1.2 s in, the confirmation never re-arms, a fresh overlap still hides the dock |
config-fuzz.sh |
PASS | full fuzz run, no torn read and no crash |
indicators.sh |
PASS | 1 / 2 / 2-on-a-plate / 6 windows → 1 / 2 / 2 / 5 marks, every centre 0.0 px off the icon's (tol 0.75) |
interactions.sh |
PASS | 16 steps: both broken-icon fixtures, app / group / folder menus, the rename field and its Escape, both popups, the unreadable folder, the settings panel and its pages, the group cycle, both unpin spellings |
ipc-roundtrip.sh |
PASS | every IPC read-back the dock exposes round-trips |
label-metrics.sh |
PASS | a real label, its metrics destroyed, reshorten() still yields the same shortened text and the same data |
layout.sh |
PASS | slot geometry and row spacing measured on the live dock |
macos-tooltip.sh |
FAIL — harness premise, see below | 12 assertions at all three tiles (app, group, folder): the callout never opens, the icon stays at scale 1.000 |
place-restore.sh |
PASS | the last window comes back identical, the root window and the focus come back, an emptied workspace does not keep the keyboard |
preset-contracts.sh |
PASS | 9 transitions (3 looks × 3), each with setup, active binding and persisted behaviour correct, clean log |
presets.sh |
PASS | every shipped look applied by name, config keys unchanged after save/list/remove |
scratchpad.sh |
PASS | the layer leaves under special:scratchpad, returns when it closes, and a dock restarted under the open scratchpad still stays hidden and then returns |
settings-navigation.sh |
PASS | all 88 search targets reach a visible row, dependency rows explained, navigation writes nothing |
tasks.sh |
PASS | reportTask → tile binding → BadgeMark → clearTask through the real IPC |
teardown.sh |
PASS | 3 probe sessions started and stopped, nothing left behind |
theme-rescan.sh |
PASS | a failed apply behind a running list read queues exactly one re-read, no stale id, no gsettings write |
Why macos-tooltip.sh is red. It fails identically on v4.13.2's own tree (12 assertions at the same tiles), and the hover components it exercises are byte-identical to v4.13.2, so it is not a regression in this release. Its premise does not hold on Hyprland 0.56.2: the suite points the cursor at the tile's centre with a compositor cursor move, and the probe's layer surface on a headless output never receives a pointer enter. Measured with a transient absolute uinput device instead, which lands exactly (400,300 / 960,540 / 1700,200 all read back through cursorpos): the pointer is on the icon's centre, the probe's own IPC reports dockVisible: true with the card at its expected place, and hover.x is still at its leave sentinel. The suite is left as it is; repairing it needs an input source this compositor delivers to a probe layer surface.
Known limitations
- The reported hover feel is unresolved. The "vibrating / laggy" hover was attributed to the
Behavior on magnifyScale; disabling it per tile wedges the dock (measured above), so that approach is out, and the trailing ease was not reproduced as a defect on the shipped tree — a hover over the row costs 12–13 jiffies / 3 s, the same as v4.13.2. A future fix needs one shared, retargeted animation instead of a binding driven frompointerXon every motion event. macos-tooltip.shcannot verify the macOS callout on this compositor for the reason above; the callout itself is unchanged from v4.13.0, where it was measured live.
Upgrading
No configuration or installation changes: existing settings, pins, presets and folder stacks carry over, and the manifest version is 4.14.0. omarchy plugin add https://github.com/thepathless/omadock.git --enable installs it on a machine that does not have it yet.