Reference · Decisions
0031: Plugins, themes and layouts
Folder plugins in a sandbox, themes for every toolkit, and layouts as data.
Status: accepted (lead decision 2026-11-01; drafted by main the same day). Builds on 0010 (widget packages), 0020 (view tree, Lua sandbox), 0021 (wopkg), 0027 (one app cannot take the desktop), 0029 (wolos builds nothing for release). An MIT-licensed desktop configuration project (credited in `docs/THIRD-PARTY.md`) is the reference for format and ecosystem; its palettes are already ported under `crates/core/src/theme/builtin/NOTICE`.
On this page
Context
the shell can be extended only by one Lua desktop card (crates/wol-lua), a JSON widget package (ADR 0010) or a theme JSON (crates/shared/src/theme.rs). The menu bar, Dock, Search, Control Center and hotkeys are closed; themes stop at toolkits (compositor colours are static in hyprland.conf); the bar and Dock edges are constants in crates/shell/src/kit/surface.rs. That reference project shows that a plugin ecosystem forms fast (1,700+ repositories in months) when the format is one folder, one manifest and one CLI, and that most plugins are small: a bar item, a panel, a theme, a web app launcher, a toggle. It runs plugins unsandboxed; WolOS will not. The lead wants: a powerful plugin and theme system integrated everywhere, an app to make themes and plugins, a layout abstraction so a Windows-like layout can come later, an API that AI agents can write plugins against easily, and plugin updates that do not wait for OS releases.
Decision
- A plugin is a folder
<id>/withplugin.tomland the files it names. Search order:~/.local/share/wolos/plugins/<id>/(user, reloaded on change),/usr/share/wolos/plugins/<id>/(packaged). Ids are reverse-DNS or<user>.<name>, lowercase;org.wolos.*is reserved. No symlinks, 1 MiB total, writes only under$XDG_STATE_HOME/wolos/plugins/<id>/. - Manifest
plugin.toml, schema 1 (frozen incrates/shared/src/plugins.rs; unknown key = load error with a sentence): headerschema, id, name, version, author, license, description, min_wolos, permissions; capability tables, each with anid:[[menubar]](script, side),[[dock]](name, icon, exec),[[widget]](script, sizes; today'slua:<id>card),[[panel]](script, anchor),[[search]](script, kind, prefix),[[tile]](icon, label, toggle),[[toggle]](on, off argv; flag file in$XDG_STATE_HOME/wolos/toggles/),[[hotkey]](default, exec, group; becomes a user-rebindableshortcuts::Custom),[[webapp]](name, url, icon, profile),[[theme]](file),[[look]](file),[[hypr]](file, whitelist ofwindowrule, layerrule, bezier, animation; neverbind, exec, plugin, env, source). The schema is published asdocs/plugins/plugin.schema.jsonand installed at/usr/share/wolos/plugins.schema.jsonso agents can validate; the whole API is documented in one file,docs/modules/plugins.md. - Code is Lua in the existing sandbox (
wol-lua: 16 MiB, 5M instructions, no io/os/require). API v1 = today'swol.*plus the view nodes that exist inshared::viewbut not in the prelude (image, toggle, slider, list, item, text_field),wol.state.get/set(per-plugin JSON, 64 KB),wol.run{argv, timeout}(permissionrun, argv only, throughwidgets::run_limited),wol.http_get(per-host grant as ADR 0010),wol.open,wol.toggle_set,wol.clipboard.text(new capability, through wold),wol.show(panel). Anything that changes the system iswol.run{"wol", ...}: it passescontroller::approveand the audit log, so plugins inherit the helper policy and appear in Settings > Assistants & Automation under the plugin id. No new privilege path. Load never acts. - Permissions and limits: declared in the manifest, shown before enabling (Store sheet,
capability_row), stored as grants inhelpers.jsonkeyed by plugin id, revocable in Settings. Each render and click is budgeted; a plugin that fails three renders in a row is disabled with a toast saying why. Hypr fragments are parsed intohyprprefskeywords, never sourced raw. - Distribution, independent of OS releases: (a) folder drop; (b)
wol plugin add <git-url|path|.wolplug>(stage, check, move; hooks never run); (c) a signed plugin index (index.json+ Ed25519 signature, key in/usr/share/wolos/keys/) consumed by a newpluginssource in wopkg (ADR 0021 recipe model) and by a Store "Plugins" page. A.wolplugis a tar.zst of the folder plusSHA256and optionalSHA256.sig(ADR 0011 form). Installs are user level;wopkg updateupdates plugins without touching pacman. Unsigned plugins are markedunreviewedlike community-collection packages and need the typedyes. The index repository (wolos-plugins) accepts pull requests validated bywol plugin checkin CI. - Agent-first CLI:
wol plugin new <id> [--kinds ...](scaffold with a passing check),wol plugin check <dir> [--json](schema, ids, sizes, symlinks, Lua loads, every entry renders with sample state, theme contrast, hypr whitelist),wol plugin run <dir> --kind <k> [--click n --state f](prints the Node tree as JSON, no display needed),list|enable|disable|remove|update|pack|preview. - Themes v2:
theme.jsonstays (tokens +extends) and gains optionalterminal(16 colours, derived when absent),wallpaper,iconTheme,lock,look. New renderers incrates/core/src/theme/: compositor colours (hyprbars bar and text, borders, shadow, written tohypr/wolos/theme.conf), lock screen, terminal palettes (foot exists; btop, alacritty, kitty, ghostty opt-in), zsh and PowerShell prompt colours. User templates~/.config/wolos/themed/<name>.tplwith{{token}},{{color3}},{{token|strip}},{{token|rgb}}rendered on every switch. Theme-from-wallpaper generator must passcheck()(contrast). Theme picker panel on ⌘⌃T, background picker on ⌘⌃W. - Layouts:
shared::layout::Layout { bar: Edge, bar_style: Menubar | Taskbar, dock: Option<Edge>, dock_merged_into_bar, workspaces_in_bar, tray_in_bar }saved inshell.json. Now:Spec::bar(edge)andSpec::dock(edge)take anchors fromLayout; the bar becomes slotsleft/centre/rightofVec<BarItem>(plugin items areBarItem::Plugin);Dock::contenttakesVec<DockItem>and an orientation; the hot zone and reveal direction follow the dock edge; the compositor's layer namespaces stay. Later, "Windows-like" =Layout{bar: Bottom, bar_style: Taskbar, dock: None, dock_merged_into_bar: true, workspaces_in_bar: true}plus oneBarItem::Tasksand a start item; no other surface changes. - Plugin Maker / Theme Studio (
wol-plugin-maker, GTK4): editor (TOML/Lua, GtkSourceView) with live preview reusing the widget gallery, a fake bar with the item inserted, Control Center tile preview and the theme preview with token sliders and live contrast; Check (same function as the CLI), Pack, Publish (opens the index pull request with the hash). Reached from Settings > Appearance ("Make a theme...") and Store > Plugins ("Make one"). - Features of the reference project ported natively in v0.12 (MIT, rewritten, not loaded as QML): web apps (
wol webapp add, frameless browser window,.desktop, Search and Dock entries), toggles as state with Control Center tiles (Stay Awake, Silence, Bar, Touchpad), theme-from-wallpaper and the two pickers, compositor colours from the theme, capture menu additions (colour picker, OCR to clipboard, QR decode kept out of history), clipboard history with images and 1-day expiry that skips verification codes, reminders (wol remind), monitor profiles with auto-apply on hotplug, update badge in the menu bar, and a reference bar pluginorg.wolos.statsthat proves the API and is the documentation example.
Consequences
one manifest, one sandbox, one view tree: plugin UI cannot leave the HIG and a broken plugin shows a sentence, not a crash. Plugins reach system services only through wol verbs, so helper policy, audit and Always-Allow cover them unchanged; a plugin that needs more is a libwol app (ADR 0020). Plugin updates ride wopkg's signed index, not OS releases, so ADR 0029 is untouched. Plugins from the reference project are ports, not loads; most are small. The layout refactor is mechanical and must land before plugin bar items, because BarItem and DockItem are the frozen seam. Memory and instruction limits of wol-lua now protect the menu bar, so the existing bomb tests are extended to every capability.
Amended 2026-11-02 (round-5 audit, C8-1, C8-2, X6-1, X6-2), what 0.12 ships against this decision:
[[look]]and[[hypr]]are planned: they parse (and the compositor-rule whitelist above is checked), but no host applies them yet, sowol plugin checkrefuses them andwol plugin newdoes not offer them.[[webapp]]is a launcher next to the person's own web apps (wol_core::plugins::sync_webapps) while the plugin is on.wol plugin addtakes a folder or a.wolplugand installs it through the Store's install step (verify_wolplug,install_plugin); unsigned needs--yes. Add by index id or git URL,wopkg updateupdating plugins and theupdateandpreviewverbs are planned; plugin updates come from the Store's Plugins page.[[toggle]],[[dock]]and[[hotkey]]commands that are notwolverbs need therunpermission, declared and granted;wol plugin checkruns scripts with inert services (nothing runs or is written), as Plugin Maker does.
Related
- Plugin API referenceThe complete manifest, permissions and Lua API for shell plugins.
- Plugin Maker and Theme StudioMake a plugin or a theme and see it live in a sandbox, then package it for the plugin index.
- 0032: Optional modulesAI, developer, creative and console stacks as removable modules and Kits.
- The wol commandEvery wol command, what it does, and which ones helpers may call.
- Optional modulesAI, developer, creative and console stacks, installed after the base and removable.
From the developer documentation, wonderlandos/wolos docs/adr/0031-shell-plugins-themes-layouts.md.