Skip to content

Publishing

Samy Lemcelli edited this page Aug 25, 2026 · 5 revisions

Publishing

Notes for listing this plugin on omarchyplugins.com, and what was done to meet the official develop and publish guidelines.

Compliance

Requirement State
manifest.json in the repo root yes
Required fields, including license all present
Reverse-domain id, not omarchy.* io.github.samy104.omarchy-spaces
Semantic versioning 0.3.0
Entry points are safe relative paths that exist verified
No symlinks in the plugin folder verified
omarchy plugin validate passes
qmllint -I $OMARCHY_PATH/shell zero errors
omarchy plugin list --json reports id, kinds, enabled verified
Survives disable, re-enable, removal verified
README with install, usage, config, removal, dependencies yes
Public repo, LICENSE, preview image yes
No omarchy.clonedFrom key verified
Public repo, LICENSE, preview image yes
Icon shipped at six theme sizes assets/icon.png
Exactly one manifest.json, at the root verified
GitHub topics omarchy, omarchy-plugin, omarchy-shell

One plugin, two placements

The marketplace requires exactly one manifest.json, at the repository root:

/^(?:[^/]+\/)?manifest\.json$/i          // root, or one directory deep
if (submission && (manifestPaths.length !== 1 || manifestPaths[0] !== "manifest.json"))
    checkError("unsupported-repository-layout", ...)

The first submission failed on exactly that. This repository shipped a second plugin under workspaces/, one directory deep, so the pattern matched two manifests and the rule rejected it.

The fix was not to bury the second manifest deeper until the regex stopped matching. That would have satisfied the check while ignoring what it is for. The two widgets are now one widget with two faces, chosen per placement by a mode setting, with allowMultiple: true in the manifest. Place it twice and you get what two plugins gave.

Omarchy's own Spacer and Indicators do the same thing, so this is the idiomatic shape rather than a workaround.

One thing that caught me out: omarchy plugin enable --section left moves an existing placement rather than adding a second one. A second placement has to be written into shell.json directly, which install.sh --replace-workspaces does.

No panel kind

The guidelines require a bar widget's panel to forward opened, open(), and close(). This plugin declares no panel kind, so that does not apply. The switcher uses the Omarchy menu through a managed block in ~/.config/omarchy/extensions/omarchy-menu.jsonc, and configuration lives in a separate GTK4 app rather than inside the shell process.

That is deliberate. A panel would have to build its own Wayland layer window, and the marketplace note is worth repeating: plugins run unsandboxed inside the long-running shell. Less code in that process is better.

Submitting

Open the submission form with the repository link, a category, and tags. Automated validation runs against the current commit before a maintainer reviews it.

Add the omarchy-shell and omarchy-plugin topics to the GitHub repository so it shows up under those topics as well.

Reading replaceable paths

The shell plugin is a long-lived process, so it never reads its config or state file with cat. A marketplace security review flagged that, correctly: cat follows symlinks, blocks forever on a FIFO, and streams a file of any size into a collector.

Both reads now go through omarchy-spaces read-file, which opens with O_NOFOLLOW and O_NONBLOCK, calls fstat on the same descriptor it will read from rather than stat-ing the path a second time, and refuses anything that is not a regular file owned by the caller within a byte limit. The limit is checked while reading as well as before, since a file can grow between the stat and the last read.

FileView on those two files is gone as well, for the same reason: it is a read of a replaceable path by the long-lived process. Directory watches remain, used only as a signal to re-read.

That costs one thing worth stating. A directory watch fires on an atomic rename, which is how the CLI and the configuration app both save, so those apply at once. An in-place hand edit changes the file without changing the directory and produces no event, so it is picked up on the service's existing tick instead, within about twenty seconds.

test/safe_read.test.py covers each refusal: symlink, FIFO, directory, device node, oversized, and the boundary where a file exactly at the limit is still read. The FIFO case runs under a timeout, because the failure mode being tested is a hang rather than a wrong answer.

Before each release

./test/run.sh
omarchy plugin validate ~/.config/omarchy/plugins/io.github.samy104.omarchy-spaces
/usr/lib/qt6/bin/qmllint -I /usr/share/omarchy/shell *.qml workspaces/*.qml
omarchy plugin list --json | grep samy104
./scripts/publish-wiki.sh

Bump version in both manifests and in the README status line together.

Documentation coverage

Every CLI command, subcommand, and config key is checked against the docs before release. The check is mechanical rather than a read-through, since a reader skims and a script does not:

python3 - <<'CHECK'
import re, glob
cli = open("bin/omarchy-spaces").read()
docs = "".join(open(f).read() for f in glob.glob("docs/*.md")) + open("README.md").read()
cmds = set(re.findall(r'"([a-z-]+)":', re.search(r'COMMANDS = \{(.*?)\n\}', cli, re.S).group(1)))
print("undocumented:", sorted(c for c in cmds if c not in docs) or "none")
CHECK

It has caught two real gaps: the discover family and switchNotification.

Clone this wiki locally