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-5096GitHub issuePublicReceived

Menu cursor jumps to top of Apps submenu ~1s after navigation, with flicker from continuous app-list rebuilds

Reported by mightywomble · filed Sep 16, 2026Closed upstream on 2026-09-16 by its own reporter mightywomble, as not planned. Closed by its own reporter — not by itself evidence of a fix.

Received and recorded verbatim. Nothing has been checked 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?
Not yet mapped to any requirement or component.
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. No decision has been recorded yet.

Original report

Menu cursor jumps to top of Apps submenu ~1s after navigation, with flicker from continuous app-list rebuildsrevision 1 · captured 2026-10-04 12:10 UTC via poll

What happened

When navigating the Omarchy menu's Apps submenu (Super+Space → Enter into Apps → Up/Down to move the cursor), the selection snaps back to the top of the list approximately 1 second after the last key press. The list also flickers visibly while open.

What was expected

The cursor should stay where the user placed it. The list should not rebuild/flicker unless the application set actually changed.

Steps to reproduce

  1. Open the menu: Super+Space
  2. Enter the Apps submenu: press Enter
  3. Move cursor to the bottom: press Up (wraps to last item)
  4. Wait 1-2 seconds without touching anything
  5. Cursor jumps back to the top of the list on its own

Root cause

Two issues, both in the Omarchy shell menu plugin (/usr/share/omarchy/shell/plugins/menu/Menu.qml):

1. `settleCursor()` doesn't clamp `selectedIndex` when the list shrinks

DesktopEntries.applications.onValuesChanged fires in continuous bursts (8+ events every ~2 seconds) even when no .desktop files have actually changed (confirmed via inotifywait — only read events, zero modify/create/delete events). Each burst triggers appLibrary.appsChanged → mergeAppRows() → rebuildDisplay() → settleCursor().

During these rebuilds the app count oscillates (observed 22–25 apps across rebuilds). When the count drops below the current selectedIndex, nextSelectable(from, 1) applies modulo arithmetic: ((selectedIndex % count) + count) % count wraps an out-of-bounds index back to 0, snapping the cursor to the top.

2. No debounce or change-detection on `onAppsChanged`

Each of the 8+ spurious onValuesChanged events per burst calls mergeAppRows() → rebuildDisplay(), which does displayModel.clear() then re-appends every row. This causes visible flicker even when the app set is identical.

Fix (applied locally via cloned plugin)

Three changes in the menu plugin:

  1. Clamp `selectedIndex` in `settleCursor()` — before calling nextSelectable, clamp to displayModel.count - 1 so an out-of-bounds index doesn't wrap to 0:
function settleCursor() {
  var from = Math.min(root.selectedIndex, displayModel.count - 1)
  if (from < 0) from = 0
  var target = root.nextSelectable(from, 1)
  root.selectedIndex = target >= 0 ? target : 0
  root.cursorActive = target >= 0
}
  1. Debounce `onAppsChanged` — coalesce the burst into a single rebuild with a 150ms Timer:
Connections {
  target: root.appLibrary
  function onAppsChanged() {
    appsChangedDebounce.restart()
  }
}

Timer {
  id: appsChangedDebounce
  interval: 150
  onTriggered: {
    if (root.providersLoaded["apps"]) root.mergeAppRows()
  }
}
  1. Skip rebuild when app set is unchanged — compute a signature of app IDs and skip mergeAppRows()/rebuildDisplay() when it matches the last signature, eliminating flicker from spurious rebuilds:
function mergeAppRows() {
  if (!root.appLibrary) return

  var rows = root.appLibrary.sortedEntries("")

  var sig = ""
  for (var s = 0; s < rows.length; s++) {
    sig += String((rows[s].entry && rows[s].entry.id) || "") + "\n"
  }
  if (sig === root.lastAppSignature && root.opened) return
  root.lastAppSignature = sig

  // ... rest of mergeAppRows unchanged
}

System details

  • Omarchy: 4.0.3rc4-1
  • Machine: MacBookPro18,4 (Apple M1 Pro, J314c)
  • Kernel: 7.1.13-3-1-ARCH (linux-asahi)
  • Hyprland: 0.56.2
  • GPU: agx-t6001 (asahi driver)
  • OS: Arch Linux ARM (Asahi)

Capture

Screen recording attached showing the cursor jumping to the top after navigation.

View raw text
## What happened

When navigating the Omarchy menu's Apps submenu (Super+Space → Enter into Apps → Up/Down to move the cursor), the selection snaps back to the top of the list approximately 1 second after the last key press. The list also flickers visibly while open.

## What was expected

The cursor should stay where the user placed it. The list should not rebuild/flicker unless the application set actually changed.

## Steps to reproduce

1. Open the menu: Super+Space
2. Enter the Apps submenu: press Enter
3. Move cursor to the bottom: press Up (wraps to last item)
4. Wait 1-2 seconds without touching anything
5. Cursor jumps back to the top of the list on its own

## Root cause

Two issues, both in the Omarchy shell menu plugin (`/usr/share/omarchy/shell/plugins/menu/Menu.qml`):

**1. `settleCursor()` doesn't clamp `selectedIndex` when the list shrinks**

`DesktopEntries.applications.onValuesChanged` fires in continuous bursts (8+ events every ~2 seconds) even when no `.desktop` files have actually changed (confirmed via `inotifywait` — only read events, zero modify/create/delete events). Each burst triggers `appLibrary.appsChanged` → `mergeAppRows()` → `rebuildDisplay()` → `settleCursor()`.

During these rebuilds the app count oscillates (observed 22–25 apps across rebuilds). When the count drops below the current `selectedIndex`, `nextSelectable(from, 1)` applies modulo arithmetic: `((selectedIndex % count) + count) % count` wraps an out-of-bounds index back to 0, snapping the cursor to the top.

**2. No debounce or change-detection on `onAppsChanged`**

Each of the 8+ spurious `onValuesChanged` events per burst calls `mergeAppRows()` → `rebuildDisplay()`, which does `displayModel.clear()` then re-appends every row. This causes visible flicker even when the app set is identical.

## Fix (applied locally via cloned plugin)

Three changes in the menu plugin:

1. **Clamp `selectedIndex` in `settleCursor()`** — before calling `nextSelectable`, clamp to `displayModel.count - 1` so an out-of-bounds index doesn't wrap to 0:

```qml
function settleCursor() {
  var from = Math.min(root.selectedIndex, displayModel.count - 1)
  if (from < 0) from = 0
  var target = root.nextSelectable(from, 1)
  root.selectedIndex = target >= 0 ? target : 0
  root.cursorActive = target >= 0
}
```

2. **Debounce `onAppsChanged`** — coalesce the burst into a single rebuild with a 150ms Timer:

```qml
Connections {
  target: root.appLibrary
  function onAppsChanged() {
    appsChangedDebounce.restart()
  }
}

Timer {
  id: appsChangedDebounce
  interval: 150
  onTriggered: {
    if (root.providersLoaded["apps"]) root.mergeAppRows()
  }
}
```

3. **Skip rebuild when app set is unchanged** — compute a signature of app IDs and skip `mergeAppRows()`/`rebuildDisplay()` when it matches the last signature, eliminating flicker from spurious rebuilds:

```qml
function mergeAppRows() {
  if (!root.appLibrary) return

  var rows = root.appLibrary.sortedEntries("")

  var sig = ""
  for (var s = 0; s < rows.length; s++) {
    sig += String((rows[s].entry && rows[s].entry.id) || "") + "\n"
  }
  if (sig === root.lastAppSignature && root.opened) return
  root.lastAppSignature = sig

  // ... rest of mergeAppRows unchanged
}
```

## System details

- **Omarchy:** 4.0.3rc4-1
- **Machine:** MacBookPro18,4 (Apple M1 Pro, J314c)
- **Kernel:** 7.1.13-3-1-ARCH (linux-asahi)
- **Hyprland:** 0.56.2
- **GPU:** agx-t6001 (asahi driver)
- **OS:** Arch Linux ARM (Asahi)

## Capture

Screen recording attached showing the cursor jumping to the top after navigation.

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

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.

No decision has been recorded yet.