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

Cloned menu plugins show an empty Apps submenu (manifestHasKind rejects model-delivered manifests)

Reported by sfk8815-create · filed Sep 11, 2026Closed upstream on 2026-09-20 by bjarneo via an automated message, as a duplicate of #10661 (R-860). GitHub close reason: completed.

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

Cloned menu plugins show an empty Apps submenu (manifestHasKind rejects model-delivered manifests)revision 1 · captured 2026-10-04 12:10 UTC via poll

System details: Intel i7-4850HQ (Intel Iris + NVIDIA GT 750M), Omarchy 4.0.3-1 (channel: stable), Hyprland 0.56.2, Quickshell 0.3.1, kernel 7.2.4-arch1-2.

What's wrong?

With the menu plugin cloned (third-party), the Apps submenu of the Omarchy menu is empty. Every other submenu works, and the shell itself is healthy: desktop entries are found at startup and the application list is populated everywhere else.

Steps to recreate:

  1. omarchy plugin clone omarchy.menu and put the clone on the bar (this also disables omarchy.menu, which is what cloning does).
  2. Restart the shell (omarchy-restart-shell).
  3. Press SUPER+SPACE and pick Apps.

Expected: the app list. Actual: an empty list ("Nothing here yet"), with no warning or error in the journal.

Cause

shell/shell.qml decides whether a plugin gets an application library by asking for the manifest kind:

appLibrary: shell.manifestHasKind(manifest, "menu")
  ? shell.pluginAppLibraryFor(cacheKey, key) : null,

manifestHasKind() requires a real JS array:

function manifestHasKind(manifest, kind) {
  return !!manifest && Array.isArray(manifest.kinds)
    && manifest.kinds.indexOf(kind) !== -1
}

Panel/overlay/menu plugins are instantiated from panelEntries through an Instantiator model (model: shell.panelEntries → panelEntry.manifest → pluginShellFor(panelEntry.manifest)). After that trip, modelData.manifest.kinds is a QV4 sequence wrapper, not a JS array. Array.isArray() is false for it while String(kinds) and kinds.indexOf("menu") still work, so manifestHasKind() returns false, appLibrary is null, and the menu's mergeAppRows() returns at if (!root.appLibrary) return — silently.

First-party menus are unaffected: pluginShellFor() early-returns the host shell for manifest.__isFirstParty, so omarchy.menu never reaches the kind check. That is why this only shows up for cloned (or any third-party) menu plugins.

Minimal reproduction, plain Quickshell, no Omarchy needed:

import Quickshell
import QtQuick

ShellRoot {
  id: sr
  function compute() {
    return [{ id: "sfk.menu", manifest: { id: "sfk.menu", kinds: ["menu", "bar-widget"] } }]
  }
  property var entries: compute()

  Instantiator {
    model: sr.entries
    active: true
    delegate: QtObject {
      required property var modelData
      Component.onCompleted: {
        var kinds = modelData.manifest.kinds
        console.log("tag: " + Object.prototype.toString.call(kinds))        // [object V4Sequence]
        console.log("Array.isArray: " + Array.isArray(kinds))                // false
        console.log("indexOf works: " + (kinds.indexOf("menu") !== -1))      // true
        console.log("manifestHasKind: " + (!!modelData.manifest && Array.isArray(kinds)
          && kinds.indexOf("menu") !== -1))                                  // false
        Qt.exit(0)
      }
    }
  }
}

Evidence from the running shell (Omarchy 4.0.3, cloned sfk.menu, omarchy.menu disabled):

  • omarchy-shell shell listPlugins reports the loaded menu as {"id":"sfk.menu","kinds":["menu","bar-widget"],"enabled":true,"firstParty":false,"clonedFrom":"omarchy.menu"}.
  • omarchy-shell shell call sfk.menu startProviderForMenu style.font followed by omarchy-shell shell call sfk.menu childCount style.font returns 12, so the bash-backed providers in the same menu work.
  • omarchy-shell shell call sfk.menu mergeAppRows followed by omarchy-shell shell call sfk.menu childCount apps returns 0, and no QML warning is logged (the appLibrary early return).
  • Loading the same Menu.qml in an isolated Quickshell with a working appLibrary injected changes childCount("apps") from 0 to 73: the menu code is fine once the library is actually handed over.

Proposed fix

shell/shell.qml, manifestHasKind() only — accept any array-like kinds:

   function manifestHasKind(manifest, kind) {
-    return !!manifest && Array.isArray(manifest.kinds)
-      && manifest.kinds.indexOf(kind) !== -1
+    // Panel/overlay/menu manifests reach the loader through an Instantiator
+    // model, which hands nested arrays back as QV4 sequence wrappers rather
+    // than JS arrays, so Array.isArray() rejects them and every cloned menu
+    // loses its application library (empty Apps submenu).
+    var kinds = manifest ? manifest.kinds : null
+    return !!kinds && typeof kinds.indexOf === "function"
+      && kinds.indexOf(kind) !== -1
   }

Verified in an isolated Quickshell against a model-delivered manifest: the old check returns false, the new one true; a manifest taken straight from installedPlugins still returns true; "service" and null still return false.

I checked the other Array.isArray(manifest.kinds) call sites in shell.qml (isBarOptionManifest, isBarWidgetPanelPlugin, ensureService, _syncServices, computePanelEntries, listPlugins); they all read manifests straight from installedPlugins or the config, so this is the only one reached with a model-delivered manifest.

I can open a PR with this change plus a before/after capture if you want it, or apply it yourselves — either works for me.

View raw text
**System details:** Intel i7-4850HQ (Intel Iris + NVIDIA GT 750M), Omarchy 4.0.3-1 (channel: stable), Hyprland 0.56.2, Quickshell 0.3.1, kernel 7.2.4-arch1-2.

## What's wrong?

With the menu plugin cloned (third-party), the **Apps** submenu of the Omarchy menu is empty. Every other submenu works, and the shell itself is healthy: desktop entries are found at startup and the application list is populated everywhere else.

Steps to recreate:

1. `omarchy plugin clone omarchy.menu` and put the clone on the bar (this also disables `omarchy.menu`, which is what cloning does).
2. Restart the shell (`omarchy-restart-shell`).
3. Press SUPER+SPACE and pick **Apps**.

Expected: the app list. Actual: an empty list ("Nothing here yet"), with no warning or error in the journal.

## Cause

`shell/shell.qml` decides whether a plugin gets an application library by asking for the manifest kind:

```qml
appLibrary: shell.manifestHasKind(manifest, "menu")
  ? shell.pluginAppLibraryFor(cacheKey, key) : null,
```

`manifestHasKind()` requires a real JS array:

```qml
function manifestHasKind(manifest, kind) {
  return !!manifest && Array.isArray(manifest.kinds)
    && manifest.kinds.indexOf(kind) !== -1
}
```

Panel/overlay/menu plugins are instantiated from `panelEntries` through an `Instantiator` model (`model: shell.panelEntries` → `panelEntry.manifest` → `pluginShellFor(panelEntry.manifest)`). After that trip, `modelData.manifest.kinds` is a QV4 sequence wrapper, not a JS array. `Array.isArray()` is false for it while `String(kinds)` and `kinds.indexOf("menu")` still work, so `manifestHasKind()` returns false, `appLibrary` is `null`, and the menu's `mergeAppRows()` returns at `if (!root.appLibrary) return` — silently.

First-party menus are unaffected: `pluginShellFor()` early-returns the host shell for `manifest.__isFirstParty`, so `omarchy.menu` never reaches the kind check. That is why this only shows up for cloned (or any third-party) menu plugins.

Minimal reproduction, plain Quickshell, no Omarchy needed:

```qml
import Quickshell
import QtQuick

ShellRoot {
  id: sr
  function compute() {
    return [{ id: "sfk.menu", manifest: { id: "sfk.menu", kinds: ["menu", "bar-widget"] } }]
  }
  property var entries: compute()

  Instantiator {
    model: sr.entries
    active: true
    delegate: QtObject {
      required property var modelData
      Component.onCompleted: {
        var kinds = modelData.manifest.kinds
        console.log("tag: " + Object.prototype.toString.call(kinds))        // [object V4Sequence]
        console.log("Array.isArray: " + Array.isArray(kinds))                // false
        console.log("indexOf works: " + (kinds.indexOf("menu") !== -1))      // true
        console.log("manifestHasKind: " + (!!modelData.manifest && Array.isArray(kinds)
          && kinds.indexOf("menu") !== -1))                                  // false
        Qt.exit(0)
      }
    }
  }
}
```

Evidence from the running shell (Omarchy 4.0.3, cloned `sfk.menu`, `omarchy.menu` disabled):

- `omarchy-shell shell listPlugins` reports the loaded menu as `{"id":"sfk.menu","kinds":["menu","bar-widget"],"enabled":true,"firstParty":false,"clonedFrom":"omarchy.menu"}`.
- `omarchy-shell shell call sfk.menu startProviderForMenu style.font` followed by `omarchy-shell shell call sfk.menu childCount style.font` returns `12`, so the bash-backed providers in the same menu work.
- `omarchy-shell shell call sfk.menu mergeAppRows` followed by `omarchy-shell shell call sfk.menu childCount apps` returns `0`, and no QML warning is logged (the `appLibrary` early return).
- Loading the same `Menu.qml` in an isolated Quickshell with a working `appLibrary` injected changes `childCount("apps")` from `0` to `73`: the menu code is fine once the library is actually handed over.

## Proposed fix

`shell/shell.qml`, `manifestHasKind()` only — accept any array-like `kinds`:

```diff
   function manifestHasKind(manifest, kind) {
-    return !!manifest && Array.isArray(manifest.kinds)
-      && manifest.kinds.indexOf(kind) !== -1
+    // Panel/overlay/menu manifests reach the loader through an Instantiator
+    // model, which hands nested arrays back as QV4 sequence wrappers rather
+    // than JS arrays, so Array.isArray() rejects them and every cloned menu
+    // loses its application library (empty Apps submenu).
+    var kinds = manifest ? manifest.kinds : null
+    return !!kinds && typeof kinds.indexOf === "function"
+      && kinds.indexOf(kind) !== -1
   }
```

Verified in an isolated Quickshell against a model-delivered manifest: the old check returns `false`, the new one `true`; a manifest taken straight from `installedPlugins` still returns `true`; `"service"` and `null` still return `false`.

I checked the other `Array.isArray(manifest.kinds)` call sites in `shell.qml` (`isBarOptionManifest`, `isBarWidgetPanelPlugin`, `ensureService`, `_syncServices`, `computePanelEntries`, `listPlugins`); they all read manifests straight from `installedPlugins` or the config, so this is the only one reached with a model-delivered manifest.

I can open a PR with this change plus a before/after capture if you want it, or apply it yourselves — either works for me.

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

Not linked to any known issue, other report or change yet.

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.