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-727GitHub issuePublicFixed

Let plugins declare menu entries in their manifest

Reported by ReidenXerx · filed Sep 10, 2026 · open at source

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?
AreaIn 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: in the audited area (menu) — decided by agents · verified; matches no known issue — decided by agents · verified; not a duplicate of another report — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.

Original report

Let plugins declare menu entries in their manifestrevision 1 · captured 2026-09-28 21:23 UTC via poll

Problem

A plugin cannot contribute anything to the Omarchy menu. The menu is built from exactly two sources:

// shell/plugins/menu/Menu.qml
property string defaultMenuPath: omarchyPath + "/default/omarchy/omarchy-menu.jsonc"
property string userMenuPath: Quickshell.env("HOME") + "/.config/omarchy/extensions/omarchy-menu.jsonc"

and the dynamic row providers are a fixed registry:

readonly property var providers: ({ "fonts": {...}, "power-profiles": {...} })

plus the QML-native apps provider. Nothing reads plugin manifests, so a plugin that wants a settings entry has only one route: edit the user's omarchy-menu.jsonc on their behalf.

Why that route is bad

Every plugin author ends up writing the same merge helper, and it has to be careful in ways that are easy to get wrong. parseMenuJsonc returns [] on any parse failure:

try { parsed = JSON.parse(stripped) } catch (e) { return [] }

That is silent and total: one plugin writing slightly malformed JSONC does not break its entries, it removes every user menu entry, with no error anywhere the user will see. A plugin that appends carelessly, or an uninstall that leaves a dangling block, takes the user's own customisations with it.

Writing three plugins, I ended up with a ~120-line helper duplicated into each: marker-delimited blocks so unrelated content and comments survive, a validator mirroring stripJsonc exactly, and rollback-to-backup if the result would not parse. That is a lot of shared risk to push onto plugin authors for what is declarative data.

Proposal

A menu block in manifest.json, merged as a third source after the default and before (or after) the user extension:

{
  "id": "reidenxerx.idle-dim",
  "kinds": ["bar-widget"],
  "menu": {
    "setup.idle-dim": { "icon": "󰣝", "label": "Idle dim", "aliases": ["dim", "brightness"] },
    "setup.idle-dim.delay.60": { "label": "1 minute", "action": "$PLUGIN_BIN/idle-dim-config set-delay 60" }
  }
}

Same entry schema as the JSONC files, so nothing new to learn. Two details that would matter in practice:

  • A path token. Entries need to invoke the plugin's own binaries, and a plugin does not know its install path when the manifest is written. Something like $PLUGIN_DIR / $PLUGIN_BIN expanded at load, mirroring how $OMARCHY_PATH already works for runtime code.
  • Namespacing. Restricting a plugin to routes under a prefix it owns (or auto-prefixing) would stop one plugin overriding system.lock, and would make removal exact: entries vanish when the plugin is disabled or removed, with no cleanup step and nothing left dangling.

Why it is worth it

Menu discoverability is how Omarchy surfaces everything else — a plugin whose settings are reachable only by right-clicking its bar widget is invisible to a user who searches. That also rules out settings for plugins with no bar widget at all (service-only), which today have nowhere to put configuration.

Related: #10021 covers keeping third-party plugins updated; this is about letting them register in the first place.

Happy to implement if the shape sounds right — the merge point in rebuildItemsFromSources looks small, and I have three plugins to test it against.

View raw text
**Problem**

A plugin cannot contribute anything to the Omarchy menu. The menu is built from exactly two sources:

```qml
// shell/plugins/menu/Menu.qml
property string defaultMenuPath: omarchyPath + "/default/omarchy/omarchy-menu.jsonc"
property string userMenuPath: Quickshell.env("HOME") + "/.config/omarchy/extensions/omarchy-menu.jsonc"
```

and the dynamic row `providers` are a fixed registry:

```qml
readonly property var providers: ({ "fonts": {...}, "power-profiles": {...} })
```

plus the QML-native `apps` provider. Nothing reads plugin manifests, so a plugin that wants a settings entry has only one route: edit the *user's* `omarchy-menu.jsonc` on their behalf.

**Why that route is bad**

Every plugin author ends up writing the same merge helper, and it has to be careful in ways that are easy to get wrong. `parseMenuJsonc` returns `[]` on any parse failure:

```js
try { parsed = JSON.parse(stripped) } catch (e) { return [] }
```

That is silent and total: one plugin writing slightly malformed JSONC does not break *its* entries, it removes **every** user menu entry, with no error anywhere the user will see. A plugin that appends carelessly, or an uninstall that leaves a dangling block, takes the user's own customisations with it.

Writing three plugins, I ended up with a ~120-line helper duplicated into each: marker-delimited blocks so unrelated content and comments survive, a validator mirroring `stripJsonc` exactly, and rollback-to-backup if the result would not parse. That is a lot of shared risk to push onto plugin authors for what is declarative data.

**Proposal**

A `menu` block in `manifest.json`, merged as a third source after the default and before (or after) the user extension:

```json
{
  "id": "reidenxerx.idle-dim",
  "kinds": ["bar-widget"],
  "menu": {
    "setup.idle-dim": { "icon": "󰣝", "label": "Idle dim", "aliases": ["dim", "brightness"] },
    "setup.idle-dim.delay.60": { "label": "1 minute", "action": "$PLUGIN_BIN/idle-dim-config set-delay 60" }
  }
}
```

Same entry schema as the JSONC files, so nothing new to learn. Two details that would matter in practice:

- **A path token.** Entries need to invoke the plugin's own binaries, and a plugin does not know its install path when the manifest is written. Something like `$PLUGIN_DIR` / `$PLUGIN_BIN` expanded at load, mirroring how `$OMARCHY_PATH` already works for runtime code.
- **Namespacing.** Restricting a plugin to routes under a prefix it owns (or auto-prefixing) would stop one plugin overriding `system.lock`, and would make removal exact: entries vanish when the plugin is disabled or removed, with no cleanup step and nothing left dangling.

**Why it is worth it**

Menu discoverability is how Omarchy surfaces everything else — a plugin whose settings are reachable only by right-clicking its bar widget is invisible to a user who searches. That also rules out settings for plugins with no bar widget at all (`service`-only), which today have nowhere to put configuration.

Related: #10021 covers keeping third-party plugins *updated*; this is about letting them register in the first place.

Happy to implement if the shape sounds right — the merge point in `rebuildItemsFromSources` looks small, and I have three plugins to test it against.

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/14277· confirmed
  • Fixed bypr/8396· confirmed
  • Related PRpr/7946· 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: in the audited area (menu)
  • Decided by agents · verified: matches no known issue
  • Decided by agents · verified: not a duplicate of another report

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