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.
Menu plugins loaded through the panel model lose shell.appLibrary (apps submenu empty)
Reported by ebin-alex · 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
What happened
After updating Omarchy 4.0.2 -> 4.0.3, a cloned menu plugin (e.g. a user clone of omarchy.menu such as ebinalex.menu) opens with an empty apps submenu. The apps section shows "Nothing here yet" instead of the installed desktop applications.
First-party built-in menus are unaffected.
Expected vs actual
- Expected: the apps submenu lists desktop applications (with icons), same as 4.0.2.
- Actual: the apps submenu renders no rows. The enclosing menu still opens and renders (so it is not a rendering/summon failure), but the app rows are missing.
Environment
- Omarchy
4.0.3-1(quickshell0.3.1-1) - Hyprland session via uwsm
- Reproduced with a cloned menu plugin:
id: ebinalex.menu,omarchy.clonedFrom: omarchy.menu,kinds: ["menu", "bar-widget"],keepLoaded: true
Steps to reproduce
- Clone a built-in menu plugin (e.g.
ebinalex.menufromomarchy.menu) so it becomes the enabled menu. - Open the menu and select the apps section.
- Observe the empty app list.
Root cause diagnosis
Traced at runtime (shell call <id> debugApps + instrumented shell.qml):
computePanelEntries()copies each plugin manifest out ofpluginRegistry.installedPluginsinto thepanelEntriesmodel property.- The
Instantiatordelegate exposes it viamodelData.manifest, which converts the manifest's nestedkindsJS array into a QVariantList. On the panel-loader pathArray.isArray(manifest.kinds)isfalse. manifestHasKind()only accepts real JS arrays, so the panel loader'spluginShellFor(panelEntry.manifest)(in the loaderonLoaded) computes a different capability profile (own-service|no-bar|no-indicators|no-menu) than the earliermenuprofile for the same plugin id.createScopedPluginShell()therefore revokes the cached PluginShellApi that carries `appLibrary` and creates a new one without it. That appLibrary-less shell is injected into the menu plugin, andmergeAppRows()early-returns on the missing app library → empty app list.
Observed logs during diagnosis:
DBG createScopedPluginShell(ebinalex.menu) ... isArr=true ... idxMenu=0 ... hasMenuKind=true profile=...|menu
DBG SITE panelLoader id=ebinalex.menu kindsArr=false
DBG createScopedPluginShell(ebinalex.menu) ... isArr=false ... hasMenuKind=false profile=...|no-menuSuggested fix (minimal, one function)
Make manifestHasKind() tolerant of array-like kinds values (QVariantList), so the profile stays consistent regardless of which path the manifest took:
function manifestHasKind(manifest, kind) {
if (!manifest) return false
var kinds = manifest.kinds
if (Array.isArray(kinds)) return kinds.indexOf(kind) !== -1
if (kinds && typeof kinds === "object" && typeof kinds.length === "number") {
for (var i = 0; i < kinds.length; i++)
if (String(kinds[i]) === kind) return true
return false
}
return false
}Verified to restore the app list (52 entries; icons render) in the affected clone.
View raw text
## What happened
After updating Omarchy 4.0.2 -> 4.0.3, a **cloned menu plugin** (e.g. a user clone of `omarchy.menu` such as `ebinalex.menu`) opens with an **empty apps submenu**. The apps section shows "Nothing here yet" instead of the installed desktop applications.
First-party built-in menus are unaffected.
## Expected vs actual
- **Expected:** the apps submenu lists desktop applications (with icons), same as 4.0.2.
- **Actual:** the apps submenu renders no rows. The enclosing menu still opens and renders (so it is not a rendering/summon failure), but the app rows are missing.
## Environment
- Omarchy `4.0.3-1` (quickshell `0.3.1-1`)
- Hyprland session via uwsm
- Reproduced with a cloned menu plugin: `id: ebinalex.menu`, `omarchy.clonedFrom: omarchy.menu`, `kinds: ["menu", "bar-widget"]`, `keepLoaded: true`
## Steps to reproduce
1. Clone a built-in menu plugin (e.g. `ebinalex.menu` from `omarchy.menu`) so it becomes the enabled menu.
2. Open the menu and select the apps section.
3. Observe the empty app list.
## Root cause diagnosis
Traced at runtime (`shell call <id> debugApps` + instrumented `shell.qml`):
- `computePanelEntries()` copies each plugin manifest out of `pluginRegistry.installedPlugins` into the `panelEntries` model property.
- The `Instantiator` delegate exposes it via `modelData.manifest`, which converts the manifest's nested `kinds` JS array into a **QVariantList**. On the panel-loader path `Array.isArray(manifest.kinds)` is `false`.
- `manifestHasKind()` only accepts real JS arrays, so the panel loader's `pluginShellFor(panelEntry.manifest)` (in the loader `onLoaded`) computes a different capability profile (`own-service|no-bar|no-indicators|no-menu`) than the earlier `menu` profile for the same plugin id.
- `createScopedPluginShell()` therefore **revokes the cached PluginShellApi that carries `appLibrary`** and creates a new one without it. That appLibrary-less shell is injected into the menu plugin, and `mergeAppRows()` early-returns on the missing app library → empty app list.
Observed logs during diagnosis:
```
DBG createScopedPluginShell(ebinalex.menu) ... isArr=true ... idxMenu=0 ... hasMenuKind=true profile=...|menu
DBG SITE panelLoader id=ebinalex.menu kindsArr=false
DBG createScopedPluginShell(ebinalex.menu) ... isArr=false ... hasMenuKind=false profile=...|no-menu
```
## Suggested fix (minimal, one function)
Make `manifestHasKind()` tolerant of array-like `kinds` values (QVariantList), so the profile stays consistent regardless of which path the manifest took:
```qml
function manifestHasKind(manifest, kind) {
if (!manifest) return false
var kinds = manifest.kinds
if (Array.isArray(kinds)) return kinds.indexOf(kind) !== -1
if (kinds && typeof kinds === "object" && typeof kinds.length === "number") {
for (var i = 0; i < kinds.length; i++)
if (String(kinds[i]) === kind) return true
return false
}
return false
}
```
Verified to restore the app list (52 entries; icons render) in the affected clone.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.