Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-1796GitHub issuePublicReceived

Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids

Reported by uMaj35ty · filed Aug 16, 2026 · open at source

Partly in the audited area (menu) — decided by agents · verified. Not validated yet.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaPartly in the audited areamenu· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. So far: partly in the audited area (menu) — decided by agents · verified; matches no known issue — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.

Original report

Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop idsrevision 1 · captured 2026-09-28 21:29 UTC via poll

Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids

Summary

On Omarchy 4.0.0 (Quattro), the menu rebuilds its app list in a tight loop —
measured every ~10ms — whenever ~/.local/share/applications/ contains
.desktop files that **override same-id entries from
/usr/share/applications/** (the standard XDG override pattern: Steam game
launchers, un-hiding NoDisplay=true entries, etc.).

While the menu is open this produces:

  • constant list flicker (rows re-instantiated several times per second)
  • the keyboard/hover selection being dragged away, often ending on the first

or last row
- it happens with no input at all (no mouse movement, no typing)
- hidden-entries.sh respawning continuously (a bash process every ~300ms,
pinning a core)
- every list driven by the shared AppLibrary is affected; the apps list
(longest) is the most visible. Lists not backed by DesktopEntries
(e.g. emojis) are unaffected.

Environment

  • Omarchy 4.0.0-1 (upgraded from 3.x to Quattro on 2026-08-16)
  • Hyprland (Quattro lua config), Quickshell shell
  • NVIDIA RTX 5060 Ti, two monitors (DP-3 2560x1440@180 scale 1, HDMI-A-1

1920x1080@60 scale 1) — the bug is resolution/scale/GPU independent as far
as I can tell; I reproduced it at 60Hz, scale 1 on both, etc.
- 88 files in ~/.local/share/applications/, of which ~80 share ids with
/usr/share/applications/ (Steam games + un-hidden system entries)

Reproduce

  1. Install Quattro.
  2. Drop a few override entries into the user dir, e.g.:
   mkdir -p ~/.local/share/applications
   for f in imv mpv btop; do
     sed 's/^NoDisplay=true/NoDisplay=false/' \
       /usr/share/applications/$f.desktop > ~/.local/share/applications/$f.desktop
   done

(On my machine the count matters: with all ~80 overrides the menu
rebuilds ~58 times in 10s; with ~40 it's ~20; with 0 it's 2 — i.e. the
loop frequency scales with the number of overridden ids.)

  1. Restart the shell, open the menu (Super+Space), enter the Apps list,

and leave it alone. Watch it flicker and the selection jump.
ps shows hidden-entries.sh Hyprland:Hyprland:omarchy respawning.

Root cause analysis (instrumented)

I cloned the omarchy.menu plugin and added counters. With the menu open
and zero input over 10 seconds:

metricwith overrideswithout overrides
rebuildDisplay() calls40–582
selectFromPointer() calls (hover events)7650
selectedIndex changes (self-oscillating 0↔1↔2)every ~300ms0

No file in ~/.local/share or ~/.config changes during the loop (checked
mtime and atime) — so this is not the "login shell touches ~/.local/share"
variant already defended against in AppLibrary.qml (the mise comment above
hiddenEntryScan). The chain is:

  1. DesktopEntries.applications (Quickshell) re-emits valuesChanged

repeatedly on its own while same-id entries exist in both the user and
system dirs. Nothing writes to disk between emissions — the churn is
internal to the model (duplicate-id resolution order instability is my
best guess).
2. AppLibrary.qml's `Connections { target: DesktopEntries.applications;
onValuesChanged: hiddenEntryScan.running = true; ... }` reruns the scan
and re-emits appsChanged.
3. Menu.qml's `Connections { target: root.appLibrary; onAppsChanged() {
if (root.providersLoaded["apps"]) root.mergeAppRows() } }` re-merges rows
and calls rebuildDisplay() — measured at up to one rebuild per
event-loop turn.
4. Every rebuild re-instantiates delegates, which fires onEntered/
onPositionChanged for the row under the (stationary) pointer. The
PointerMoveGate filters the first sample, but subsequent delegate
swaps move the local coordinate by a full row height, so ~half the hover
events pass moved() and rewrite selectedIndex; revealCursor() then
scrolls, which sweeps more delegates under the pointer and keeps the
churn alive. That is the visible "flicker + selection dragged to the
bottom/first row".

Workaround I'm running (menu plugin clone)

Two changes in a cloned omarchy.menu plugin stop it completely (58 → 4
rebuilds per 10s with all files restored, selection stable, hover still
works):

// Menu.qml — pause list rebuilds while the menu is open; the open path
// force-reloads instead:
Connections {
  target: root.appLibrary
  function onAppsChanged() {
    if (root.opened) return
    if (root.providersLoaded["apps"]) root.mergeAppRows()
  }
}
// in the open path, next to invalidateVolatileProvider(activeMenu):
root.providersLoaded[activeMenu] = false   // force fresh pull on open

Plus a cooldown in a local PointerMoveGate copy (disarm() sets a 250ms
window in which moved() returns false, also called from
ListView.onContentYChanged) so hover churn from rebuilds/scrolling can no
longer steal the selection.

Suggested fix upstream

Any of these would help; the first is the cheapest and matches what I
validated:

  1. In Menu.qml, don't rebuild from appsChanged while the menu is open;

refresh on open instead (appsChanged while closed still merges, and
installing an app writes a desktop file, so the closed-state signal
fires and data stays fresh).
2. Debounce hiddenEntryScan / coalesce valuesChanged in AppLibrary.qml
(a scan restart per emission with no dedup window amplifies the churn).
3. The deeper question: why DesktopEntries.applications re-emits
valuesChanged with no filesystem change when duplicate ids are present
— that may belong upstream in Quickshell, but Omarchy can shield itself
with (1)/(2).

Happy to provide the instrumented logs or test a fix.

View raw text
# Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids

## Summary

On Omarchy 4.0.0 (Quattro), the menu rebuilds its app list in a tight loop —
measured **every ~10ms** — whenever `~/.local/share/applications/` contains
`.desktop` files that **override same-id entries from
`/usr/share/applications/`** (the standard XDG override pattern: Steam game
launchers, un-hiding `NoDisplay=true` entries, etc.).

While the menu is open this produces:

- constant list flicker (rows re-instantiated several times per second)
- the keyboard/hover selection being dragged away, often ending on the first
  or last row
- it happens **with no input at all** (no mouse movement, no typing)
- `hidden-entries.sh` respawning continuously (a bash process every ~300ms,
  pinning a core)
- every list driven by the shared `AppLibrary` is affected; the apps list
  (longest) is the most visible. Lists not backed by `DesktopEntries`
  (e.g. emojis) are unaffected.

## Environment

- Omarchy 4.0.0-1 (upgraded from 3.x to Quattro on 2026-08-16)
- Hyprland (Quattro lua config), Quickshell shell
- NVIDIA RTX 5060 Ti, two monitors (DP-3 2560x1440@180 scale 1, HDMI-A-1
  1920x1080@60 scale 1) — the bug is resolution/scale/GPU independent as far
  as I can tell; I reproduced it at 60Hz, scale 1 on both, etc.
- 88 files in `~/.local/share/applications/`, of which ~80 share ids with
  `/usr/share/applications/` (Steam games + un-hidden system entries)

## Reproduce

1. Install Quattro.
2. Drop a few override entries into the user dir, e.g.:

   ```bash
   mkdir -p ~/.local/share/applications
   for f in imv mpv btop; do
     sed 's/^NoDisplay=true/NoDisplay=false/' \
       /usr/share/applications/$f.desktop > ~/.local/share/applications/$f.desktop
   done
   ```

   (On my machine the count matters: with all ~80 overrides the menu
   rebuilds ~58 times in 10s; with ~40 it's ~20; with 0 it's 2 — i.e. the
   loop frequency scales with the number of overridden ids.)

3. Restart the shell, open the menu (`Super+Space`), enter the Apps list,
   and leave it alone. Watch it flicker and the selection jump.
   `ps` shows `hidden-entries.sh Hyprland:Hyprland:omarchy` respawning.

## Root cause analysis (instrumented)

I cloned the `omarchy.menu` plugin and added counters. With the menu open
and **zero input** over 10 seconds:

| metric | with overrides | without overrides |
| --- | --- | --- |
| `rebuildDisplay()` calls | 40–58 | 2 |
| `selectFromPointer()` calls (hover events) | 765 | 0 |
| `selectedIndex` changes (self-oscillating 0↔1↔2) | every ~300ms | 0 |

No file in `~/.local/share` or `~/.config` changes during the loop (checked
mtime and atime) — so this is not the "login shell touches ~/.local/share"
variant already defended against in `AppLibrary.qml` (the mise comment above
`hiddenEntryScan`). The chain is:

1. `DesktopEntries.applications` (Quickshell) re-emits `valuesChanged`
   repeatedly on its own while same-id entries exist in both the user and
   system dirs. Nothing writes to disk between emissions — the churn is
   internal to the model (duplicate-id resolution order instability is my
   best guess).
2. `AppLibrary.qml`'s `Connections { target: DesktopEntries.applications;
   onValuesChanged: hiddenEntryScan.running = true; ... }` reruns the scan
   and re-emits `appsChanged`.
3. `Menu.qml`'s `Connections { target: root.appLibrary; onAppsChanged() {
   if (root.providersLoaded["apps"]) root.mergeAppRows() } }` re-merges rows
   and calls `rebuildDisplay()` — measured at up to one rebuild per
   event-loop turn.
4. Every rebuild re-instantiates delegates, which fires `onEntered`/
   `onPositionChanged` for the row under the (stationary) pointer. The
   `PointerMoveGate` filters the first sample, but subsequent delegate
   swaps move the local coordinate by a full row height, so ~half the hover
   events pass `moved()` and rewrite `selectedIndex`; `revealCursor()` then
   scrolls, which sweeps more delegates under the pointer and keeps the
   churn alive. That is the visible "flicker + selection dragged to the
   bottom/first row".

## Workaround I'm running (menu plugin clone)

Two changes in a cloned `omarchy.menu` plugin stop it completely (58 → 4
rebuilds per 10s with all files restored, selection stable, hover still
works):

```qml
// Menu.qml — pause list rebuilds while the menu is open; the open path
// force-reloads instead:
Connections {
  target: root.appLibrary
  function onAppsChanged() {
    if (root.opened) return
    if (root.providersLoaded["apps"]) root.mergeAppRows()
  }
}
// in the open path, next to invalidateVolatileProvider(activeMenu):
root.providersLoaded[activeMenu] = false   // force fresh pull on open
```

Plus a cooldown in a local `PointerMoveGate` copy (`disarm()` sets a 250ms
window in which `moved()` returns false, also called from
`ListView.onContentYChanged`) so hover churn from rebuilds/scrolling can no
longer steal the selection.

## Suggested fix upstream

Any of these would help; the first is the cheapest and matches what I
validated:

1. In `Menu.qml`, don't rebuild from `appsChanged` while the menu is open;
   refresh on open instead (appsChanged while closed still merges, and
   installing an app writes a desktop file, so the closed-state signal
   fires and data stays fresh).
2. Debounce `hiddenEntryScan` / coalesce `valuesChanged` in `AppLibrary.qml`
   (a scan restart per emission with no dedup window amplifies the churn).
3. The deeper question: why `DesktopEntries.applications` re-emits
   `valuesChanged` with no filesystem change when duplicate ids are present
   — that may belong upstream in Quickshell, but Omarchy can shield itself
   with (1)/(2).

Happy to provide the instrumented logs or test a fix.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

  • Related PRpr/7413· confirmed
  • Related PRpr/14992· confirmed

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Decided by agents · verified: partly in the audited area (menu)
  • Decided by agents · verified: matches no known issue

The outcome (valid, duplicate, not a bug, …) is not decided yet.