diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md index a94d18a6..3914db63 100644 --- a/docs/omarchy-shell.md +++ b/docs/omarchy-shell.md @@ -61,7 +61,7 @@ editor, confirmation, and output stay visible. Cloning `omarchy.clock`, for example, creates and switches to `~/.config/omarchy/plugins/local.clock/`, names it `My Clock`, and preserves the built-in IPC identity so existing shortcuts keep working. Saving files in -a `local.*` clone reloads its code automatically, and removing an active clone +any installed plugin reloads its code automatically, and removing an active clone switches back to its built-in source. For a bar widget, on and off means its place in the bar. Everything else is diff --git a/shell/README.md b/shell/README.md index 10a4b811..641f6e3a 100644 --- a/shell/README.md +++ b/shell/README.md @@ -155,7 +155,7 @@ the interactive picker, then opens the new `local.*` directory in `$EDITOR`. Existing shortcuts and shell IPC calls made to the built-in id are routed to the enabled clone, so cloning does not require changing its callers. Removing an active clone switches back to its built-in source. -Saving a file anywhere inside a `local.*` plugin reloads plugin code +Saving a file anywhere under `~/.config/omarchy/plugins/` reloads plugin code automatically; `omarchy-shell shell rescanPlugins` remains available to force a reload. First-party plugins under `shell/plugins/` are discovered the same way and load diff --git a/shell/services/PluginRegistry.qml b/shell/services/PluginRegistry.qml index c4cacd7b..38d67d5f 100644 --- a/shell/services/PluginRegistry.qml +++ b/shell/services/PluginRegistry.qml @@ -668,9 +668,11 @@ QtObject { function localPluginIdForPath(filePath) { var base = pluginsDir.replace(/\/$/, "") + "/" var path = String(filePath || "").trim() - if (path.indexOf(base + "local.") !== 0) return "" + if (path.indexOf(base) !== 0) return "" var relative = path.slice(base.length) + // Hidden entries are not plugins: clone staging dirs, remove backups. + if (relative.indexOf(".") === 0) return "" if (relative.indexOf("/.git/") !== -1 || relative.endsWith("/.git")) return "" var slash = relative.indexOf("/") diff --git a/test/shell.d/fixtures/plugin-registry/shell.qml b/test/shell.d/fixtures/plugin-registry/shell.qml index 973cd3a7..b1d45eb8 100644 --- a/test/shell.d/fixtures/plugin-registry/shell.qml +++ b/test/shell.d/fixtures/plugin-registry/shell.qml @@ -337,10 +337,11 @@ ShellRoot { root.assertTrue(root.config.disabledPlugins === undefined, "disabling a multi-kind widget records nothing else") root.assertTrue(registry.isEnabled("omarchy.hybrid"), "a multi-kind built-in remains loadable without its widget") - var localBase = registry.pluginsDir + "/local.clock" - root.assertEqual(registry.localPluginIdForPath(localBase + "/BarWidget.qml"), "local.clock", "local clone changes are watched") - root.assertEqual(registry.localPluginIdForPath(registry.pluginsDir + "/acme.clock/BarWidget.qml"), "", "installed plugins are not treated as local clones") - root.assertEqual(registry.localPluginIdForPath(localBase + "/.git/index"), "", "clone git metadata is ignored") + var cloneBase = registry.pluginsDir + "/dhh.clock" + root.assertEqual(registry.localPluginIdForPath(cloneBase + "/BarWidget.qml"), "dhh.clock", "personal clone changes are watched") + root.assertEqual(registry.localPluginIdForPath(registry.pluginsDir + "/acme.clock/BarWidget.qml"), "acme.clock", "installed plugin changes are watched") + root.assertEqual(registry.localPluginIdForPath(cloneBase + "/.git/index"), "", "plugin git metadata is ignored") + root.assertEqual(registry.localPluginIdForPath(registry.pluginsDir + "/.clone.abc123/manifest.json"), "", "hidden staging and backup dirs are ignored") root.assertTrue(changeCount > 0, "registry emits change notifications") writeResult() diff --git a/test/shell.d/runtime-smoke-test.sh b/test/shell.d/runtime-smoke-test.sh index 5a0be906..47523c2b 100755 --- a/test/shell.d/runtime-smoke-test.sh +++ b/test/shell.d/runtime-smoke-test.sh @@ -53,12 +53,14 @@ cp -a "$ROOT/shell" "$test_root/shell" ln -s "$ROOT/config" "$test_root/config" ln -s "$ROOT/bin" "$test_root/bin" -hot_reload_dir="$test_home/.config/omarchy/plugins/local.hot-reload" +# Every plugin under ~/.config/omarchy/plugins hot-reloads, whoever wrote it. +hot_reload_id="acme.hot-reload" +hot_reload_dir="$test_home/.config/omarchy/plugins/$hot_reload_id" mkdir -p "$hot_reload_dir" -cat >"$hot_reload_dir/manifest.json" <<'JSON' +cat >"$hot_reload_dir/manifest.json" </dev/null | - jq -r '.[] | select(.id == "local.hot-reload") | .name' 2>/dev/null || true) + jq -r --arg id "$hot_reload_id" '.[] | select(.id == $id) | .name' 2>/dev/null || true) [[ $hot_reload_name == "After Hot Reload" ]] && break if ! kill -0 "$QS_PID" 2>/dev/null; then - fail_with_log "test shell exited while reloading a changed local clone" + fail_with_log "test shell exited while reloading a changed installed plugin" fi sleep 0.1 done [[ $hot_reload_name == "After Hot Reload" ]] || - fail_with_log "local clone changes reload without an explicit rescan" -pass "local clone changes reload without an explicit rescan" + fail_with_log "installed plugin changes reload without an explicit rescan" +pass "installed plugin changes reload without an explicit rescan" -[[ $(shell_ipc shell setPluginEnabled local.hot-reload true) == "ok" ]] || - fail_with_log "local clone could not be enabled" +[[ $(shell_ipc shell setPluginEnabled "$hot_reload_id" true) == "ok" ]] || + fail_with_log "installed plugin could not be enabled" [[ $(shell_ipc shell summon omarchy.emojis "{}") == "ok" ]] || fail_with_log "calls to a cloned source id do not reach its enabled clone" shell_ipc_quiet shell hide omarchy.emojis >/dev/null -shell_ipc_quiet shell setPluginEnabled local.hot-reload false >/dev/null +shell_ipc_quiet shell setPluginEnabled "$hot_reload_id" false >/dev/null pass "shell IPC routes built-in ids to enabled clones" shell_config=$(shell_ipc shell listShellConfig)