Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA 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.
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.
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
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:
omarchy plugin clone omarchy.menuand put the clone on the bar (this also disablesomarchy.menu, which is what cloning does).- Restart the shell (
omarchy-restart-shell). - 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 listPluginsreports 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.fontfollowed byomarchy-shell shell call sfk.menu childCount style.fontreturns12, so the bash-backed providers in the same menu work.omarchy-shell shell call sfk.menu mergeAppRowsfollowed byomarchy-shell shell call sfk.menu childCount appsreturns0, and no QML warning is logged (theappLibraryearly return).- Loading the same
Menu.qmlin an isolated Quickshell with a workingappLibraryinjected changeschildCount("apps")from0to73: 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.