#16105

Cloned bar widget never renders: plugin QML outside the config folder, host injection fails, and bar.centerAnchor is never rewritten

OpenCatoGaustadopened 1d ago

System

  • Omarchy 4.0.6-1 (Arch Linux, Hyprland)
  • Shell: Quickshell, launched as quickshell -n -p /usr/share/omarchy/shell
  • No hardware-specific factors; reproduced entirely with stock Omarchy plugins

Summary

omarchy plugin clone <built-in-bar-widget> reports success and omarchy plugin enable <clone> reports success, the clone is listed as enabled, and the clone's id appears in bar.layout, but the cloned widget is never instantiated. The built-in widget keeps rendering instead. The clone is therefore an inert entry that cannot be edited into effect.

Two independent defects combine, plus a third that silently degrades bar centering:

  1. User plugin QML lives outside the Quickshell config folder and the scanner refuses to use it as a component source.
  2. Host property injection throws for non-first-party widgets, so even when the component loads, the slot never becomes live.
  3. omarchy plugin enable rewrites bar.layout.* but not bar.centerAnchor, so anchoring silently degrades.

Steps to reproduce

omarchy plugin clone omarchy.clock      # -> "Cloned omarchy.clock to ~/.config/omarchy/plugins/<user>.clock and switched to <user>.clock"
omarchy plugin enable <user>.clock      # -> "Enabled <user>.clock"

Then add a definitive marker to the clone so instantiation is unambiguous:

# ~/.config/omarchy/plugins/<user>.clock/BarWidget.qml
# insert into the widget root:
Component.onCompleted: { console.log("CLOCKDIAG file=clone instantiated") }

Restart the shell or let the live reload fire, then inspect the journal:

journalctl -b -u omarchy-shell --no-pager | grep -E "CLOCKDIAG|outside of the config folder|Cannot assign"

Expected

The clone is instantiated and the marker is logged; the bar renders the clone's BarWidget.qml instead of the built-in one. Per shell/README.md, cloning is documented to switch the widget from the built-in to the personal copy ("Cloning switches from the built-in to the new personal plugin").

Actual

  • The marker never appears in the journal — the clone's BarWidget.qml is never instantiated.
  • The built-in /usr/share/omarchy/shell/plugins/panels/clock/BarWidget.qml is instantiated and keeps rendering.
  • omarchy plugin list shows the clone enabled=true and the built-in enabled=false, i.e. state claims the clone is in use.

Journal evidence

WARN quickshell.qmlscanner: Module path "/home/<user>/.config/omarchy/plugins/<user>.clock" is outside of the config folder.
WARN scene: @plugins/bar/Bar.qml[2019:-1]: Error: Cannot assign [undefined] to QObject* (exception occurred during delayed function evaluation)
WARN scene: QML IpcHandler at file:///home/<user>/.config/omarchy/plugins/<user>.clock/BarWidget.qml[129:3]:
     Handler was registered but will not be used because another handler is registered for target omarchy.clock

The Cannot assign [undefined] to QObject* warning appears repeatedly (once per bar slot injection attempt) and is the visible symptom of defect 2.

Root cause

Defect 1 — component source is outside the Quickshell config folder

syncPluginWidgets() registers the widget component under the manifest id:

  • shell.qml:1393 — var registryKey = String(manifest.id)
  • shell.qml:1514 — shell.barWidgetRegistry.register(registryKey, comp, meta)

but the widget's QML file lives at ~/.config/omarchy/plugins/<clone>/BarWidget.qml, while Quickshell runs with -p /usr/share/omarchy/shell. The scanner logs the path as outside the config folder, so the component cannot be used as a live source. First-party widgets under /usr/share/omarchy/shell/plugins/** are inside it and load cleanly.

Defect 2 — host property injection throws for non-first-party widgets

Bar.qml:2016-2023:

function injectProps() {
  var target = activeItem
  if (!target) return
  if ("bar" in target) target.bar = firstParty
    ? root : root.pluginBarApiFor(pluginApiId, moduleName, registered)
  if ("moduleName" in target) target.moduleName = moduleName
  if ("settings" in target) target.settings = moduleSettings
}

For a cloned widget firstParty is false (Bar.qml:1796, from metadata.firstParty: !!manifest.__isFirstParty at shell.qml:1415), so the pluginBarApiFor(...) branch runs. The journal records the assignment producing undefined at Bar.qml[2019], so target.bar is never set, and the widget cannot become a functional module.

Note the registry lookup itself is not the problem — it lines up correctly:

  • Bar.qml:1801-1806 looks up var registryName = root.canonicalWidgetId(moduleName) in barWidgetRegistry.widgets
  • Bar.qml:1792 — moduleName is the layout entry id (the clone's own id, via BarModel.entryId)
  • Util.qml:70-72 — canonicalWidgetId is identity (return String(id || ""))

So BarWidgetRegistry genuinely holds the clone key and the slot genuinely looks up the clone key. The clone id is not rewritten to its clonedFrom source on the bar-slot path: resolveEnabledId (PluginRegistry.qml:171-182), restoreCloneSource (:441-472), and activeCloneFor (:426-439) are invoked only from setEnabled and the IPC summon/hide/toggle/service paths — never from Bar.qml.

This means the clone is not instantiable for any built-in bar widget, not just the clock.

Defect 3 — bar.centerAnchor is left pointing at the built-in id

bar.centerAnchor is consumed at Bar.qml:600 (centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")), and:

  • Bar.qml:1532-1536 — findCenterAnchorEntry() → entryIndex(layoutEntries("center"), root.centerAnchor)
  • Bar.qml:1552 — readonly property bool hasAnchor: root.entryIndex(entries, root.centerAnchor) !== -1
  • Bar.qml:1572-1599 — the anchored layout is shown only when hasAnchor; otherwise the fallback ModuleList renders all center entries

When a widget that is the literal centerAnchor is cloned, setEnabled's clone path rewrites only the bar.layout.* entry id (PluginRegistry.qml:533 — replacement.id = key) and bar.id (:513-515). Nothing writes bar.centerAnchor:

$ grep -rn "centerAnchor" /usr/share/omarchy
plugins/bar/Bar.qml:49       centerAnchor: "omarchy.clock",     # fallbackBarConfig default
plugins/bar/Bar.qml:600      centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")
plugins/bar/README.md:29     "centerAnchor": "omarchy.clock",
shell/README.md:245          "centerAnchor": "omarchy.clock",

Because the layout now contains the clone id but centerAnchor still names the built-in id, entryIndex returns -1, hasAnchor becomes false, and the intended anchor-and-flank centering silently degrades to plain group centering — with no user-visible warning that the anchor is dangling.

Impact

  • omarchy plugin clone is not usable for any built-in bar widget in this version: the clone is created, enabled, listed as replacing the built-in, and still renders nothing. Any user customization workflow based on cloning bar widgets (documented in shell/README.md) is silently dead.
  • The failure is silent: clone and enable both exit 0, notifications confirm the switch, omarchy plugin list reports the desired state, and only persistent journal warnings hint at the problem.
  • Cloning the clock specifically also leaves bar.centerAnchor dangling, degrading center-section layout.

Suggested direction

  • Load user plugin components in a way Quickshell accepts (or import them into the config folder's module path), so a third-party widget's QML is a usable component source.
  • Make pluginBarApiFor(...) return a usable facade for third-party bar widgets, or make injectProps tolerate a missing facade instead of assigning undefined to bar.
  • In the clone/enable path, rewrite bar.centerAnchor from the source id to the clone id (or, better, resolve the anchor through the same clone-aware routing that IPC already uses via resolveEnabledId), and warn when the configured anchor does not exist in the layout.

Activity

0

No activity yet.