diff --git a/AGENTS.md b/AGENTS.md
index 634091d..f2d45f0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,7 @@
This is the repo-wide rulebook for any agent (or human) editing Ruixen. It's
a map, not a tutorial — deeper detail already lives in `README.md`,
-`COMPATIBILITY.md`, `docs/CONTROL.md`, `docs/LAUNCHER.md`, `docs/KEYBINDS.md`,
+`dev/README.md` (layout, workflow, testing), `COMPATIBILITY.md`, `docs/CONTROL.md`, `docs/LAUNCHER.md`, `docs/KEYBINDS.md`,
and the tests themselves. Read those when you need specifics; read this
first to know which rules exist at all.
diff --git a/README.md b/README.md
index dbd4eda..99c5480 100644
--- a/README.md
+++ b/README.md
@@ -1,454 +1,188 @@
-# Ruixen Shell
+
Ruixen
-A connected bar, notch, and settings app for Omarchy — an OLED-black, unified
-visual layer that runs as plugins inside the Omarchy shell you already use.
+An integrated desktop suite for Omarchy.
+
+
+A cohesive set of native Omarchy plugins that changes how the desktop looks,
+feels and works, without replacing the shell underneath it.
+
-
-
- Bar — Docked
- |
-
-
- Bar — Float
- |
-
-
- Notch Dashboard
- |
-
-
-
-
- Launcher
- |
-
-
- Settings
- |
- |
+ |
+ |
+ |
-## What's included
-
-- **`ruixen.bar`** — the top bar itself: app launcher, dot-style
- workspace indicator, pinned quick-launch apps, weather, clock, and a
- settings shortcut, all in one connected pill layout. Fresh installs use
- floating glass pills with accent-toned icons.
-- **`ruixen.notch`** — a center-notch dashboard with metrics, wallpapers
- (and a theme switcher on the same tab: a segmented control above the
- search box lists every installed Omarchy theme; clicking one applies it
- via Omarchy's own `omarchy-theme-set`),
- storage, music control, a notification history card (attaches to
- Omarchy's own notification service, adding read/unread tracking and a
- deeper backlog on top of it), and a Kanban board (see below), expanding
- from the bar.
-- **`ruixen.launcher`** — a Raycast/Spotlight-style command palette in a
- frosted-glass card (real Hyprland compositor blur, not a fake overlay):
- fuzzy-searches Omarchy menu actions and installed apps from one overlay,
- plus a dedicated Search Files mode. Searches both filenames (multi-word
- queries match across path components, not just the final segment) and
- file *contents* (ripgrep-powered, ranked together — content matches
- fill in around real filename hits rather than needing a separate mode),
- across every auto-discovered drive (an internal HDD, a USB stick)
- individually or all at once — plus any custom folder you add yourself,
- or exclude, from this plugin's own built-in Settings extension (its
- File Search page). Filter by file
- type/hidden-files/names-or-contents from a small control row, or type
- the same filters directly into the query (`type:image`, `in:Home`,
- `hidden:true`, ...). Selecting a file shows a real preview — an
- extracted video frame, an image thumbnail, or a text/markdown/JSON
- snippet — plus metadata (type, dimensions/duration, created/modified,
- permissions), and a contextual action menu (Tab, or right-click a row)
- for opening its containing folder or copying its path/name. Its own
- built-in Settings extension is also the full settings app for this
- shell — Profile, Bar, Audio, Wi-Fi, Bluetooth, Display, Night Light,
- Plugins — replacing the default Omarchy settings panel entirely. Full
- reference: [`docs/LAUNCHER.md`](docs/LAUNCHER.md).
-- **`ruixen.frame-widget`** — the OLED-black screen frame that ties the bar
- and notch together visually.
-- **`ruixen.pinnedapps`** — quick-launch row for apps pinned in the notch's
- own app launcher.
-- **`ruixen.pluginpins`** — a pin/unpin dropdown on the bar for any other
- installed bar-widget plugin (yours or a third party's) — install
- something new, pin it from here, no shell.json editing required.
-- **`ruixen.peripherals`** — battery percentage for wireless mice,
- keyboards, headsets and controllers (Bluetooth and USB receivers alike),
- pin the ones you care about to show inline on the bar. Detection reads
- `/sys` directly rather than Quickshell's own Bluetooth/UPower bindings,
- which don't reliably cover every wireless peripheral — ported from
- [xgborgeso/omarchy-peripheral-batteries](https://github.com/xgborgeso/omarchy-peripheral-batteries)
- (MIT license).
-- **Tray widgets** — `ruixen.tray`, `ruixen.stayawake`,
- `ruixen.quickactions`, `ruixen.weather`, `ruixen.applauncher`,
- `ruixen.settingsbutton`. `ruixen.stayawake` (and any stock Omarchy widget
- it sits next to, like the AI usage indicator) is pinned on or off through
- `ruixen.pluginpins` above, not a separate settings toggle.
-
-`ruixen.media` backs `ruixen.notch`'s own music control as a background
-service — it never shows a bar icon of its own by design (an earlier,
-oversized play/pause badge was retired), so it's locked in Settings' Plugins
-list with no toggle.
-
-Every plugin shares the same OLED-black background, corner radii, and motion
-language, so they read as one shell instead of a pile of separate widgets.
-
-## Documentation
-
-- [`docs/LAUNCHER.md`](docs/LAUNCHER.md) — the Ruixen Launcher command
- palette in full: Applications/Commands search, Search Files (multi-word
- matching, filters, keyboard-first query operators), the contextual
- actions menu, and configuring which folders it actually searches.
-- [`docs/KEYBINDS.md`](docs/KEYBINDS.md) — ready-to-use Hyprland keybind
- recipes: the Ruixen Launcher command palette, Ruixen Settings (and
- jumping straight to one page), the notch dashboard, the app launcher.
-- [`docs/CONTROL.md`](docs/CONTROL.md) — how every plugin here is
- controllable over a plain CLI call (`omarchy-shell
- [args]`), the same mechanism a keybind, a script, or an AI agent all use
- identically, plus the Kanban board's full command reference as a worked
- example.
-
-## Install
-
-Ruixen Shell targets Omarchy `4.0.0-1` and has been live-verified through
-Omarchy `4.0.4-1`.
-Install from source:
-
-```bash
-git clone https://github.com/gitcoder89431/ruixen-shell.git
-cd ruixen-shell
-./install.sh
-```
-
-An AUR package is planned but not yet published — cloning from source is the
-only install path right now.
-
-The installer copies each plugin into `~/.config/omarchy/plugins/`, backs up
-anything it would overwrite, merges Ruixen's bar/plugin config into your
-existing `shell.json` rather than replacing it outright (any unrelated bar
-widgets, plugins, or idle settings you already had survive), applies a
-matching Hyprland window look (rounded corners + blur, see below — also
-backed up if you already have a `looknfeel.lua`), and restarts the Omarchy
-shell.
-
-After installing, add a keybind of your own for opening the Ruixen Launcher
-command palette (nothing opens it out of the box — the installer
-deliberately doesn't touch your Hyprland config), Ruixen Settings, or
-anything else — the app launcher, jumping straight to one settings page. See
-[`docs/KEYBINDS.md`](docs/KEYBINDS.md) for ready-to-use recipes, e.g.:
-
-```lua
-o.bind("SUPER + R", "Ruixen Launcher", "omarchy-shell shell toggle ruixen.launcher")
-```
-
-If you want the recommended keybinds installed automatically, use the opt-in
-flag:
-
-```bash
-./install.sh --with-launcher-keybind
-```
-
-That flag only appends keys that are free: `SUPER+R` for Ruixen Launcher and
-`SUPER+SHIFT+R` for Ruixen Settings. If either key is already bound, the
-installer leaves that key untouched and prints the current binding.
-
-Want to see exactly what it would do first, without changing anything?
-
-```bash
-./install.sh --dry-run
-```
-
-Reports Omarchy version/dependency status, plugin manifest validation
-(run for real, read-only), which plugins would install fresh vs. replace
-an existing copy, whether `shell.json` would be created or merged (and
-what would actually change), and the Hyprland look'n'feel plan — then
-exits having touched nothing.
-
-### If a previous run was interrupted
-
-An ordinary failure (a bad plugin, `omarchy restart shell` erroring out)
-already rolls back cleanly on its own — you'll see that reported and don't
-need to do anything special. A hard interruption is different: a closed
-terminal, `kill -9`, a crash, or power loss skips that rollback entirely,
-since there's no chance for it to run. If `install.sh`, `update.sh` (which
-hands off to `install.sh`) or `uninstall.sh`
-detects that its own previous run never reached the end, it refuses to
-proceed and tells you exactly which step it had reached:
-
-```
-refusing to proceed: a previous install run appears to have been interrupted before finishing.
- started: 2026-09-30T03:15:00Z
- reached: 4/7 applying shell layout
-```
-
-This is almost always safe to just continue from — every plugin is fully
-re-copied from source on each run, and `shell.json`/looknfeel writes are
-atomic, so nothing can be left half-written. Run `./ruixen-doctor.sh`
-first if you want to double-check (read-only, reports plugin drift and
-runtime health), then re-run the same command with
-`--acknowledge-interrupted` to continue. All of `install.sh`, `update.sh`
-and `uninstall.sh` accept it, and reject any option they don't recognize
-(`--help` lists what each takes).
-
-## Updating
-
-```bash
-./update.sh
-```
-
-`./update.sh --dry-run` previews it first: current vs. candidate revision,
-then the same install plan above for whatever is currently on disk
-(pulling itself is skipped, so it can't preview code not yet checked out —
-noted explicitly in its own output).
-
-Pulls the latest changes and reinstalls — same backup-then-merge
-behavior as `install.sh` itself, so it's always safe to re-run. Only
-works from your existing cloned checkout (it just wraps `git pull` +
-`./install.sh`), so don't delete the folder after installing.
-
-If something looks like it didn't update, or a plugin looks out of
-date:
-
-```bash
-./ruixen-doctor.sh
-```
-
-A read-only diagnostic report — checks nothing changes. Prints your
-git status vs the remote, whether each deployed plugin's actual file
-content matches this checkout's own source byte-for-byte (catches an
-update that silently didn't finish, even when nothing's version number
-changed), backup history, the current bar layout (ids only), and basic
-runtime health. Safe to paste the output anywhere — no paths,
-hostnames, or personal config values are ever printed.
+One desktop, three themes: the bar, notch and frame follow whichever Omarchy theme you pick.
-If doctor finds drift, fix it directly:
+## What is Ruixen?
-```bash
-./ruixen-repair.sh --dry-run # report what's broken, change nothing
-./ruixen-repair.sh # actually fix it
-```
+Ruixen runs as plugins inside the Omarchy shell you already use. A bar, a
+notch dashboard, a launcher, a settings app and a handful of small tools
+share one look and one set of behaviors, so they feel like a single desktop
+instead of a pile of widgets.
-Detects any plugin whose deployed files don't match this checkout
-(missing entirely or content mismatch) and a dangling `looknfeel.lua`
-symlink, then fixes them by running `install.sh` itself — the same
-deploy path every install/update already uses, so `shell.json` and any
-third-party bar entries are preserved exactly as they always are.
+It is **not** a replacement shell, **not** just a theme, and **not** a
+random plugin pack. Turn any piece off, or go back to stock Omarchy in one
+command.
-## Disabling / going back to Omarchy defaults
+## The experience
-Nothing here is a one-way door.
+### A connected bar and notch
-**Turn individual plugins off, keep everything installed:**
-
-```bash
-omarchy plugin disable ruixen.notch
-omarchy plugin disable ruixen.frame-widget
-# same for any of the tray widgets: ruixen.tray, ruixen.weather, etc.
+A bar of floating glass pills (or one docked strip flush with the frame)
+wraps the screen in a matching frame, black or following your theme. In the middle, the notch expands
+into a dashboard: music, calendar, notifications, quick toggles, volume,
+wallpapers with a theme switcher, system health, and a Kanban board.
-omarchy plugin enable ruixen.notch # turns it back on
-```
+
+
+
+
+ Floating bar
+ |
+
+
+ Docked bar
+ |
+
+
+ Notch dashboard
+ |
+
+
-If a plugin stops updating after toggling it a few times, run
-`omarchy restart shell` — a full restart always clears it.
+The notch also holds a three-column **Kanban board** that you and your coding
+agent can both drive from a keybind or a script.
-**Switch the bar back to stock Omarchy:**
+
+
+
-```bash
-omarchy bar defaults
-```
+### One launcher for apps, commands, files and clipboard
-Use `omarchy bar defaults`, not `omarchy plugin enable omarchy.bar` — that
-command only swaps the bar engine and leaves Ruixen's widget layout in
-place, which looks broken rather than default. `omarchy bar defaults`
-resets everything (id, layout, position, transparency) in one shot.
+A Raycast/Spotlight-style palette on real compositor blur. Search Omarchy
+actions and installed apps, search files by name **and** contents across
+every drive with live previews, browse Clipboard History, and browse, preview and
+install community themes from the bjarneo collection.
-To bring Ruixen's own bar back afterward, just run `./install.sh` again.
+
+
+
+
+ Search Files: names and contents, with previews
+ |
+
+
+ Clipboard History
+ |
+
+
+
+
+ Theme browser
+ |
+
+
+ The palette: actions, apps and extensions
+ |
+
+
-**Fully remove a plugin's files:**
+### Settings that live in the same place
-```bash
-omarchy plugin remove ruixen.bar
-```
+Profile, bar layout, window look, audio, Wi-Fi, Bluetooth, display, night
+light and plugins, in the same overlay as the launcher.
-Backs the plugin up rather than deleting it outright (to
-`~/.config/omarchy/plugins/..bak.`) — disable/enable and the
-bar reset just flip settings, this is the only step that touches files at
-all.
+
+
+
-**Uninstall everything in one shot:**
+### A drop pocket for files
-```bash
-./uninstall.sh
-```
+Drag files in from any app, drag them back out into another app or a
+terminal. The **Shelf** grows out of the frame at the notch, only remembers
+paths (it never copies or moves anything), and is readable and writable by
+your coding agent.
-Switches back to the built-in Omarchy bar, removes every Ruixen plugin's
-files for real (unlike a bare `omarchy plugin remove`, which just backs a
-plugin up instead of deleting it — see above; this deletes those backups
-too, so nothing lingers), restores your original Hyprland window look (or
-Omarchy's own default if you never had one), and restarts the shell. Only
-works from your existing cloned checkout, same as `update.sh` — the
-checkout itself is left alone, delete it yourself afterward if you don't
-want it around. Same in-app path also lives in Ruixen Settings' own
-Plugins page, behind a typed confirmation.
-
-`./uninstall.sh --dry-run` previews exactly what would happen first: the
-bar host it would restore, which of your own widgets it would preserve,
-which Ruixen plugin files it would remove, any leftover Ruixen entry it
-would sweep out of `shell.json`'s `plugins[]` array, and the look'n'feel
-restore plan — nothing is changed.
-
-## Docked bar mode (experimental)
-
-By default the left and right icon groups float as separate pills, inset
-from the frame. Docked mode merges each side into one continuous shape
-flush with the frame's corners instead — like the notch, just with one
-shoulder curve per side instead of two. Not the default look, but worth
-trying:
+
+
+
-```bash
-./dev/ruixen-bar-mode.sh docked # merged pills, flush with the frame
-./dev/ruixen-bar-mode.sh floating # back to the default separate pills
-./dev/ruixen-bar-mode.sh status # show which one is active
-```
+### And more
-No restart needed either way — it's a live config reload.
+- **Kanban** in the notch: a three-column board you and an agent can both drive.
+- **Wallpapers**, including muted looping video, and an audio visualizer.
+- **Plugin pinning**: install any Omarchy bar widget, pin it from the bar, no config editing.
+- **Peripherals**: battery levels for wireless mice, keyboards, headsets and controllers.
+- **Window look**: rounded corners and blur to match the frame, or stock square.
-## Bar style
+Everything is controllable from a keybind, a script or an agent through
+`omarchy-shell`: see [Control](docs/CONTROL.md).
-The normal style is `notch`: the center island stays visible and the bar keeps
-its center reserved. `fullbar` is the saved full-width statusline skin from the
-old sharp+docked experiment. It hides the notch overlay and lets the bar own the
-center space again.
+## Quick install
```bash
-./dev/ruixen-bar-style.sh notch # current island/notch skin
-./dev/ruixen-bar-style.sh fullbar # full-width statusline skin, no notch
-./dev/ruixen-bar-style.sh status # show which one is active
+git clone https://github.com/gitcoder89431/ruixen-shell.git
+cd ruixen-shell
+./install.sh
```
-This is independent from `./dev/ruixen-bar-mode.sh docked|floating` and independent
-from Hyprland sharp/rounded window corners.
-
-## Kanban board
-
-`ruixen.notch`'s dashboard has a 4th tab: a fixed 3-column board (Todo / In
-Progress / Done — Tab cycles through all 4 tabs, or click the column-icon in
-the left rail). It's agent-native — every mutation (add, move, rename,
-priority, due date, label, description) is a plain IPC call a script or
-agent can drive — and it's fully editable in the notch itself now too:
-per-column add buttons, hover edit/delete on each card, and a done/total
-progress bar. Renaming a column stays CLI-only. Full command reference and
-how the click model works: [`docs/CONTROL.md`](docs/CONTROL.md).
-
-## Shelf (drop pocket)
+Want to see what it will do first? `./install.sh --dry-run` changes nothing.
+The installer backs up what it replaces, merges into your existing
+`shell.json` instead of overwriting it, and restarts the Omarchy shell.
+Details, updating and uninstalling: [Installation](docs/INSTALLATION.md).
-`ruixen.shelf` is a panel that grows out of the frame at the notch's position — the notch's expanded silhouette, with concave wing shoulders, hanging from the top edge: drag files in
-from any app, drag them back out into another app or a terminal. It
-remembers file paths, never copies anything. Drag local files over the
-collapsed notch and the Shelf opens so you can drop them in and see them land
-(it hides again if you drag back out without dropping). It's
-agent-readable too — `omarchy-shell ruixen.shelf list` shows an agent what
-you dropped, and `add /abs/path` lets it put a file on the shelf for you to
-drag out. It's its own plugin rather than a notch tab so other apps stay
-reachable for drag-and-drop. Details: [`docs/CONTROL.md`](docs/CONTROL.md).
+## Getting started
-## Window look'n'feel (Hyprland)
-
-Ruixen also rounds window corners and adds blur, to match the frame/bar.
-Fresh installs default to the half-radius look (12px). Toggle it
-independently of the plugins above:
+Nothing opens the launcher out of the box, because the installer doesn't
+touch your Hyprland config unless you ask. To install the recommended keys
+that are still free:
```bash
-hyprland/ruixen-lookfeel.sh on # rounded corners + blur, matches the frame
-hyprland/ruixen-lookfeel.sh half # rounded corners at half the radius (12px), same border/blur/shadow/animations
-hyprland/ruixen-lookfeel.sh off # stock Omarchy: square corners, no blur
-hyprland/ruixen-lookfeel.sh square # square corners, but keeps the thin border/blur/shadow/animations
-hyprland/ruixen-lookfeel.sh status # show which one is active
+./install.sh --with-launcher-keybind
```
-`half` is the default middle step between `on` and `square` — the same rounded
-look at half the corner radius, for when 24px reads too soft and sharp reads
-too stark. `square` is for anyone who wants stock Omarchy's own square corners
-without giving up the rest of Ruixen's look. The screen frame's own corner
-rounding follows whichever of the four is active automatically when the bar is
-floating. When the bar is docked, the frame's corner always stays rounded
-regardless of which variant is active -- docked mode's own wider gaps already
-keep real window corners well clear of that curve, so nothing clips, and it
-keeps the docked bar's own corner (always rounded) visually consistent with the
-frame right next to it.
-
-## Requirements
+| Key | Opens |
+|---|---|
+| `SUPER+R` | Ruixen Launcher |
+| `SUPER+SHIFT+R` | Ruixen Settings |
+| `SUPER+CTRL+SPACE` | Wallpapers picker |
+| `SUPER+D` | Shelf |
-- Omarchy `4.0.0-1` (or a nearby build of the same shell generation) --
- `install.sh` checks this and warns (doesn't block) if it detects
- something outside that range
-- Quickshell, as provided by Omarchy
-- `jq` -- the installer itself needs it to merge into your existing
- `shell.json` rather than overwrite it
+Or bind your own: see [Keybinds](docs/KEYBINDS.md). Then take the
+[manual](docs/README.md) for a tour.
-`install.sh` also checks a few optional, feature-specific dependencies
-and warns (without failing) if any are missing, so you know up front
-rather than discovering it later when a feature quietly doesn't work:
+## Documentation
-| Missing | What's unavailable |
+| I want to... | Go to |
|---|---|
-| `ffmpeg` | Video/gif wallpaper poster generation (current/background and the lock screen won't reflect the active video/gif; the moving wallpaper itself is unaffected by this one) |
-| `qt6-multimedia` (package, not command -- install a backend with it, e.g. `qt6-multimedia-ffmpeg`) | Video AND gif wallpaper playback both fail silently to start -- not part of Omarchy's own base install, only present if some other app happened to pull it in |
-| `curl` | Weather data, avatar image download in Settings |
-| `python3` | The bar's docked-mode toggle |
-| `fastfetch` | Less detail on the health page's system-info panel |
-| `cava` | The Desktop audio visualizer; the rest of Ruixen remains usable |
-
-## Running tests
+| Find my way around | [Manual index](docs/README.md) |
+| Install, update, repair or uninstall | [Installation](docs/INSTALLATION.md) |
+| Search files, use the clipboard | [Launcher](docs/LAUNCHER.md) |
+| Set up keybinds | [Keybinds](docs/KEYBINDS.md) |
+| Change the bar or window look | [Customizing](docs/CUSTOMIZATION.md) |
+| See what each plugin does | [Plugins and features](docs/PLUGINS.md) |
+| Script it or hand it to an agent | [Control](docs/CONTROL.md) |
+| Check version support | [`COMPATIBILITY.md`](COMPATIBILITY.md) |
+| Build or contribute | [`dev/`](dev/README.md) |
+| Rules for coding agents | [`AGENTS.md`](AGENTS.md) |
-```bash
-./tests/run-all.sh
-```
-
-Runs almost everything CI runs (`.github/workflows/ci.yml`) in one go:
-shell script lint (`bash -n` + ShellCheck, when installed), plugin
-manifest validation, the JS model tests, and the installer lifecycle/
-config/uninstall-restore tests. Each suite can also be run on its own --
-see `tests/*.sh`, every file has its own header comment explaining
-what it covers.
-
-CI runs one additional step this doesn't: `tests/host-contract-
-regression.sh` (issue #34), which fetches real source from
-`github.com/basecamp/omarchy` at the exact commit `COMPATIBILITY.md`
-records as reviewed and checks it still matches the host contracts this
-repo depends on. Deliberately excluded from `run-all.sh` since it needs
-network access and GitHub API auth that a local run shouldn't require --
-run it directly (`./tests/host-contract-regression.sh`) if you want to
-check it yourself.
-
-The installer tests (`tests/install-lifecycle.sh`, `tests/shell-json-
-merge.sh`, `tests/looknfeel-preserve.sh`, `tests/uninstall-bar-
-restore.sh`) run against a throwaway fake `$HOME`/directory tree, never
-your real config, so they're safe to run anywhere including this repo's
-own checkout.
-
-### Manual QA: the Desktop audio visualizer
-
-`tests/cava-*.sh` cover the visualizer's own state/lifecycle wiring
-statically, but a few things only really show up live. A couple of
-minutes, not a long soak:
+## Requirements
-```
-1. Off -> Bars -> Segments -> Wave
-2. switch 64 <-> 96 bands
-3. pause/resume audio
-4. enable/disable a few times
-5. enter/exit fullscreen
-6. kill cava once and confirm only one replacement process appears
-```
+Omarchy `4.0.0-1` or a nearby build of the same shell generation (live-verified
+through `4.0.4-1`), Quickshell as provided by Omarchy, and `jq`. A few optional
+tools unlock specific features (video wallpapers, the audio visualizer,
+weather); the installer warns about any that are missing. Full list:
+[Installation](docs/INSTALLATION.md#requirements).
## Credits
- **[Omarchy](https://omarchy.org)** ([github.com/basecamp/omarchy](https://github.com/basecamp/omarchy)) — the Arch/Hyprland desktop this whole project is built on top of. `omarchy-shell`, Omarchy's own Quickshell-based bar/notch/notification runtime, is what every plugin here actually loads into.
- **[Ambxst](https://github.com/Axenide/Ambxst)** (by Axenide) — UI/UX design inspiration for several `ruixen.notch` panels (the dashboard layout, calendar, metrics page, wallpapers picker). Ambxst's own code is AGPL-3.0 licensed; ruixen-shell's implementations are written independently, not derived from its source.
-- **[xgborgeso/omarchy-peripheral-batteries](https://github.com/xgborgeso/omarchy-peripheral-batteries)** (MIT) — `ruixen.peripherals`'s detection logic is ported from this project (see above, and the plugin's own source header for the full attribution).
+- **[xgborgeso/omarchy-peripheral-batteries](https://github.com/xgborgeso/omarchy-peripheral-batteries)** (MIT) — `ruixen.peripherals`'s detection logic is ported from this project (see [Plugins](docs/PLUGINS.md), and the plugin's own source header for the full attribution).
## License
diff --git a/dev/README.md b/dev/README.md
new file mode 100644
index 0000000..11a080a
--- /dev/null
+++ b/dev/README.md
@@ -0,0 +1,137 @@
+# Developing Ruixen
+
+For people changing Ruixen rather than using it: where things live, how it
+plugs into Omarchy, the dev scripts, and how to test. The repo-wide rules for
+humans and coding agents are in [`AGENTS.md`](../AGENTS.md) at the repository
+root: read that first, it is deliberately short. User-facing docs are in
+[`docs/`](../docs/README.md).
+
+## How Ruixen plugs into Omarchy
+
+Ruixen is a set of plugins that load **inside Omarchy's existing, long-lived
+Quickshell process**. Nothing here starts its own shell. Each plugin is a
+directory with a `manifest.json` (an Omarchy plugin id such as `ruixen.bar`)
+and a QML entry point that is an `Item`, not a standalone `ShellRoot`.
+Overlay-kind plugins (launcher, shelf) expose the lifecycle the host expects:
+`open(payloadJson)`, `close()`, `toggle(payloadJson)`.
+
+Control goes over `omarchy-shell ` (see
+[`docs/CONTROL.md`](../docs/CONTROL.md)); shared state lives in small
+versioned files under `~/.local/state/ruixen/`. `AGENTS.md` §1-§4 holds the
+actual rules, including which Omarchy internals not to depend on.
+
+## Repository layout
+
+```text
+bars/v2/ruixen.bar/ the bar, the screen frame, docked chrome
+bars/widgets/ruixen.*/ bar widgets and the notch (ruixen.notch)
+ruixen.launcher/ launcher, Search Files, Settings, Clipboard, Theme browser
+ruixen.shelf/ the drop-pocket overlay
+ruixen.wallpaper/ video wallpaper support
+ruixen.cava/ audio visualizer overlay
+hyprland/ window look'n'feel variants + ruixen-lookfeel.sh
+theme-overlays/ per-theme tweaks applied on install
+lib/ installer helpers (shell.json merge, restore, lock, journal)
+install.sh update.sh lifecycle scripts (all have --dry-run)
+uninstall.sh
+ruixen-doctor.sh read-only diagnostic report
+ruixen-repair.sh redeploys drifted plugins via install.sh
+dev/ developer helper scripts (below)
+tests/ contract, model and lifecycle tests; run-all.sh
+docs/ user manual
+preview/ README images
+```
+
+Where a given feature lives: the **bar** and **frame** in
+`bars/v2/ruixen.bar/` (`Bar.qml`, `BarPanel.qml`, `FrameWindow.qml`); the
+**notch** and its Kanban/wallpapers/health tabs in
+`bars/widgets/ruixen.notch/Overlay.qml`; the **launcher** in
+`ruixen.launcher/Launcher.qml` with each extension under
+`ruixen.launcher/extensions/`; the **Settings** app in
+`ruixen.launcher/extensions/settings/`; the **Shelf** in `ruixen.shelf/`.
+The bar, notch and Shelf are one visual surface by design: read `AGENTS.md`
+§9 before touching any of them.
+
+## Dev scripts
+
+| Script | What it does |
+|---|---|
+| `dev/ruixen-bar-mode.sh docked\|floating\|status` | Switch the bar between merged (docked) and separate (floating) pills; live, no restart |
+| `dev/ruixen-bar-style.sh notch\|fullbar\|status` | Switch between the notch skin and the full-width statusline skin |
+| `hyprland/ruixen-lookfeel.sh on\|half\|square\|off\|status` | Window corner/blur variants |
+| `./ruixen-doctor.sh` | Read-only report: git state, plugin drift by content hash, bar layout, runtime health, clipboard capture |
+| `./ruixen-repair.sh [--dry-run]` | Redeploy plugins that drifted from this checkout |
+
+The first two are described for users in
+[`docs/CUSTOMIZATION.md`](../docs/CUSTOMIZATION.md).
+
+## Local workflow
+
+1. Edit in a checkout, run `./install.sh --dry-run` to see what a deploy
+ would change, then `./install.sh` to deploy it.
+2. For runtime, QML or IPC changes do a real `omarchy restart shell`
+ rather than trusting hot reload, and check
+ `journalctl --user -b 0` for `is not a type`, `Type ... unavailable`,
+ `TypeError` and binding loops. Neither `omarchy plugin validate` nor the
+ test suite compiles QML, so a missing `import` only shows up there.
+3. `omarchy plugin validate ` after manifest or structure changes.
+4. Run the tests (below) before pushing.
+
+The full checklist, with the incidents behind each item, is `AGENTS.md` §8.
+
+## Compatibility
+
+[`COMPATIBILITY.md`](../COMPATIBILITY.md) is the ledger of which Omarchy and
+Quickshell versions have actually been reviewed. Don't bump its reviewed
+versions until that version has really been reviewed; a bump is a claim of
+verification.
+
+## Running tests
+
+```bash
+./tests/run-all.sh
+```
+
+Runs almost everything CI runs (`.github/workflows/ci.yml`) in one go:
+shell script lint (`bash -n` + ShellCheck, when installed), plugin
+manifest validation, the JS model tests, and the installer lifecycle/
+config/uninstall-restore tests. Each suite can also be run on its own --
+see `tests/*.sh`, every file has its own header comment explaining
+what it covers.
+
+CI runs one additional step this doesn't: `tests/host-contract-
+regression.sh` (issue #34), which fetches real source from
+`github.com/basecamp/omarchy` at the exact commit `COMPATIBILITY.md`
+records as reviewed and checks it still matches the host contracts this
+repo depends on. Deliberately excluded from `run-all.sh` since it needs
+network access and GitHub API auth that a local run shouldn't require --
+run it directly (`./tests/host-contract-regression.sh`) if you want to
+check it yourself.
+
+The installer tests (`tests/install-lifecycle.sh`, `tests/shell-json-
+merge.sh`, `tests/looknfeel-preserve.sh`, `tests/uninstall-bar-
+restore.sh`) run against a throwaway fake `$HOME`/directory tree, never
+your real config, so they're safe to run anywhere including this repo's
+own checkout.
+
+### Live checks
+
+`tests/live-shelf-ipc.sh` drives the Shelf's real IPC boundary against a
+running shell, so it is outside `run-all.sh` and CI (it skips itself when no
+shell is running). Run it after a real `omarchy restart shell` when you
+change the Shelf.
+
+### Manual QA: the Desktop audio visualizer
+
+`tests/cava-*.sh` cover the visualizer's own state/lifecycle wiring
+statically, but a few things only really show up live. A couple of
+minutes, not a long soak:
+
+```
+1. Off -> Bars -> Segments -> Wave
+2. switch 64 <-> 96 bands
+3. pause/resume audio
+4. enable/disable a few times
+5. enter/exit fullscreen
+6. kill cava once and confirm only one replacement process appears
+```
diff --git a/docs/CUSTOMIZATION.md b/docs/CUSTOMIZATION.md
new file mode 100644
index 0000000..cea8a4b
--- /dev/null
+++ b/docs/CUSTOMIZATION.md
@@ -0,0 +1,63 @@
+# Customizing Ruixen
+
+Look-and-feel options that sit on top of a working install. Most of these
+are also reachable from **Ruixen Settings** (open it with the keybind from
+[`KEYBINDS.md`](KEYBINDS.md)); the commands below are the same switches from
+a terminal. Back to the [manual index](README.md).
+
+## Docked bar mode (experimental)
+
+By default the left and right icon groups float as separate pills, inset
+from the frame. Docked mode merges each side into one continuous shape
+flush with the frame's corners instead — like the notch, just with one
+shoulder curve per side instead of two. Not the default look, but worth
+trying:
+
+```bash
+./dev/ruixen-bar-mode.sh docked # merged pills, flush with the frame
+./dev/ruixen-bar-mode.sh floating # back to the default separate pills
+./dev/ruixen-bar-mode.sh status # show which one is active
+```
+
+No restart needed either way — it's a live config reload.
+
+## Bar style
+
+The normal style is `notch`: the center island stays visible and the bar keeps
+its center reserved. `fullbar` is the saved full-width statusline skin from the
+old sharp+docked experiment. It hides the notch overlay and lets the bar own the
+center space again.
+
+```bash
+./dev/ruixen-bar-style.sh notch # current island/notch skin
+./dev/ruixen-bar-style.sh fullbar # full-width statusline skin, no notch
+./dev/ruixen-bar-style.sh status # show which one is active
+```
+
+This is independent from `./dev/ruixen-bar-mode.sh docked|floating` and independent
+from Hyprland sharp/rounded window corners.
+
+## Window look'n'feel (Hyprland)
+
+Ruixen also rounds window corners and adds blur, to match the frame/bar.
+Fresh installs default to the half-radius look (12px). Toggle it
+independently of the plugins above:
+
+```bash
+hyprland/ruixen-lookfeel.sh on # rounded corners + blur, matches the frame
+hyprland/ruixen-lookfeel.sh half # rounded corners at half the radius (12px), same border/blur/shadow/animations
+hyprland/ruixen-lookfeel.sh off # stock Omarchy: square corners, no blur
+hyprland/ruixen-lookfeel.sh square # square corners, but keeps the thin border/blur/shadow/animations
+hyprland/ruixen-lookfeel.sh status # show which one is active
+```
+
+`half` is the default middle step between `on` and `square` — the same rounded
+look at half the corner radius, for when 24px reads too soft and sharp reads
+too stark. `square` is for anyone who wants stock Omarchy's own square corners
+without giving up the rest of Ruixen's look. The screen frame's own corner
+rounding follows whichever of the four is active automatically when the bar is
+floating. When the bar is docked, the frame's corner always stays rounded
+regardless of which variant is active -- docked mode's own wider gaps already
+keep real window corners well clear of that curve, so nothing clips, and it
+keeps the docked bar's own corner (always rounded) visually consistent with the
+frame right next to it.
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
new file mode 100644
index 0000000..19da130
--- /dev/null
+++ b/docs/INSTALLATION.md
@@ -0,0 +1,218 @@
+# Installing and maintaining Ruixen
+
+Everything about getting Ruixen onto your machine, keeping it current, and
+getting back out again. For the two-minute version, see the
+[README](../README.md#quick-install). Back to the [manual index](README.md).
+
+## Install
+
+Ruixen targets Omarchy `4.0.0-1` and has been live-verified through
+Omarchy `4.0.4-1`.
+Install from source:
+
+```bash
+git clone https://github.com/gitcoder89431/ruixen-shell.git
+cd ruixen-shell
+./install.sh
+```
+
+An AUR package is planned but not yet published — cloning from source is the
+only install path right now.
+
+The installer copies each plugin into `~/.config/omarchy/plugins/`, backs up
+anything it would overwrite, merges Ruixen's bar/plugin config into your
+existing `shell.json` rather than replacing it outright (any unrelated bar
+widgets, plugins, or idle settings you already had survive), applies a
+matching Hyprland window look (rounded corners + blur, see below — also
+backed up if you already have a `looknfeel.lua`), and restarts the Omarchy
+shell.
+
+After installing, add a keybind of your own for opening the Ruixen Launcher
+command palette (nothing opens it out of the box — the installer
+deliberately doesn't touch your Hyprland config), Ruixen Settings, or
+anything else — the app launcher, jumping straight to one settings page. See
+[`docs/KEYBINDS.md`](KEYBINDS.md) for ready-to-use recipes, e.g.:
+
+```lua
+o.bind("SUPER + R", "Ruixen Launcher", "omarchy-shell shell toggle ruixen.launcher")
+```
+
+If you want the recommended keybinds installed automatically, use the opt-in
+flag:
+
+```bash
+./install.sh --with-launcher-keybind
+```
+
+That flag only appends keys that are free: `SUPER+R` for Ruixen Launcher,
+`SUPER+SHIFT+R` for Ruixen Settings, `SUPER+CTRL+SPACE` for the wallpapers
+picker and `SUPER+D` for the Shelf. If a key is already bound, the installer
+leaves that key untouched and prints the current binding.
+
+Want to see exactly what it would do first, without changing anything?
+
+```bash
+./install.sh --dry-run
+```
+
+Reports Omarchy version/dependency status, plugin manifest validation
+(run for real, read-only), which plugins would install fresh vs. replace
+an existing copy, whether `shell.json` would be created or merged (and
+what would actually change), and the Hyprland look'n'feel plan — then
+exits having touched nothing.
+
+### If a previous run was interrupted
+
+An ordinary failure (a bad plugin, `omarchy restart shell` erroring out)
+already rolls back cleanly on its own — you'll see that reported and don't
+need to do anything special. A hard interruption is different: a closed
+terminal, `kill -9`, a crash, or power loss skips that rollback entirely,
+since there's no chance for it to run. If `install.sh`, `update.sh` (which
+hands off to `install.sh`) or `uninstall.sh`
+detects that its own previous run never reached the end, it refuses to
+proceed and tells you exactly which step it had reached:
+
+```
+refusing to proceed: a previous install run appears to have been interrupted before finishing.
+ started: 2026-09-30T03:15:00Z
+ reached: 4/7 applying shell layout
+```
+
+This is almost always safe to just continue from — every plugin is fully
+re-copied from source on each run, and `shell.json`/looknfeel writes are
+atomic, so nothing can be left half-written. Run `./ruixen-doctor.sh`
+first if you want to double-check (read-only, reports plugin drift and
+runtime health), then re-run the same command with
+`--acknowledge-interrupted` to continue. All of `install.sh`, `update.sh`
+and `uninstall.sh` accept it, and reject any option they don't recognize
+(`--help` lists what each takes).
+
+## Updating
+
+```bash
+./update.sh
+```
+
+`./update.sh --dry-run` previews it first: current vs. candidate revision,
+then the same install plan above for whatever is currently on disk
+(pulling itself is skipped, so it can't preview code not yet checked out —
+noted explicitly in its own output).
+
+Pulls the latest changes and reinstalls — same backup-then-merge
+behavior as `install.sh` itself, so it's always safe to re-run. Only
+works from your existing cloned checkout (it just wraps `git pull` +
+`./install.sh`), so don't delete the folder after installing.
+
+If something looks like it didn't update, or a plugin looks out of
+date:
+
+```bash
+./ruixen-doctor.sh
+```
+
+A read-only diagnostic report — checks nothing changes. Prints your
+git status vs the remote, whether each deployed plugin's actual file
+content matches this checkout's own source byte-for-byte (catches an
+update that silently didn't finish, even when nothing's version number
+changed), backup history, the current bar layout (ids only), basic
+runtime health, and whether Omarchy's clipboard capture is alive (useful when
+Clipboard History shows old entries but never new copies). Safe to paste the output anywhere — no paths,
+hostnames, or personal config values are ever printed.
+
+If doctor finds drift, fix it directly:
+
+```bash
+./ruixen-repair.sh --dry-run # report what's broken, change nothing
+./ruixen-repair.sh # actually fix it
+```
+
+Detects any plugin whose deployed files don't match this checkout
+(missing entirely or content mismatch) and a dangling `looknfeel.lua`
+symlink, then fixes them by running `install.sh` itself — the same
+deploy path every install/update already uses, so `shell.json` and any
+third-party bar entries are preserved exactly as they always are.
+
+## Disabling / going back to Omarchy defaults
+
+Nothing here is a one-way door.
+
+**Turn individual plugins off, keep everything installed:**
+
+```bash
+omarchy plugin disable ruixen.notch
+omarchy plugin disable ruixen.shelf
+# same for any of the tray widgets: ruixen.tray, ruixen.weather, etc.
+
+omarchy plugin enable ruixen.notch # turns it back on
+```
+
+If a plugin stops updating after toggling it a few times, run
+`omarchy restart shell` — a full restart always clears it.
+
+**Switch the bar back to stock Omarchy:**
+
+```bash
+omarchy bar defaults
+```
+
+Use `omarchy bar defaults`, not `omarchy plugin enable omarchy.bar` — that
+command only swaps the bar engine and leaves Ruixen's widget layout in
+place, which looks broken rather than default. `omarchy bar defaults`
+resets everything (id, layout, position, transparency) in one shot.
+
+To bring Ruixen's own bar back afterward, just run `./install.sh` again.
+
+**Fully remove a plugin's files:**
+
+```bash
+omarchy plugin remove ruixen.bar
+```
+
+Backs the plugin up rather than deleting it outright (to
+`~/.config/omarchy/plugins/..bak.`) — disable/enable and the
+bar reset just flip settings, this is the only step that touches files at
+all.
+
+**Uninstall everything in one shot:**
+
+```bash
+./uninstall.sh
+```
+
+Switches back to the built-in Omarchy bar, removes every Ruixen plugin's
+files for real (unlike a bare `omarchy plugin remove`, which just backs a
+plugin up instead of deleting it — see above; this deletes those backups
+too, so nothing lingers), restores your original Hyprland window look (or
+Omarchy's own default if you never had one), and restarts the shell. Only
+works from your existing cloned checkout, same as `update.sh` — the
+checkout itself is left alone, delete it yourself afterward if you don't
+want it around. Same in-app path also lives in Ruixen Settings' own
+Plugins page, behind a typed confirmation.
+
+`./uninstall.sh --dry-run` previews exactly what would happen first: the
+bar host it would restore, which of your own widgets it would preserve,
+which Ruixen plugin files it would remove, any leftover Ruixen entry it
+would sweep out of `shell.json`'s `plugins[]` array, and the look'n'feel
+restore plan — nothing is changed.
+
+## Requirements
+
+- Omarchy `4.0.0-1` (or a nearby build of the same shell generation) --
+ `install.sh` checks this and warns (doesn't block) if it detects
+ something outside that range
+- Quickshell, as provided by Omarchy
+- `jq` -- the installer itself needs it to merge into your existing
+ `shell.json` rather than overwrite it
+
+`install.sh` also checks a few optional, feature-specific dependencies
+and warns (without failing) if any are missing, so you know up front
+rather than discovering it later when a feature quietly doesn't work:
+
+| Missing | What's unavailable |
+|---|---|
+| `ffmpeg` | Video/gif wallpaper poster generation (current/background and the lock screen won't reflect the active video/gif; the moving wallpaper itself is unaffected by this one) |
+| `qt6-multimedia` (package, not command -- install a backend with it, e.g. `qt6-multimedia-ffmpeg`) | Video AND gif wallpaper playback both fail silently to start -- not part of Omarchy's own base install, only present if some other app happened to pull it in |
+| `curl` | Weather data, avatar image download in Settings |
+| `python3` | The bar's docked-mode toggle |
+| `fastfetch` | Less detail on the health page's system-info panel |
+| `cava` | The Desktop audio visualizer; the rest of Ruixen remains usable |
diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md
new file mode 100644
index 0000000..71162f4
--- /dev/null
+++ b/docs/PLUGINS.md
@@ -0,0 +1,106 @@
+# Plugins and features reference
+
+What each Ruixen plugin does, and the longer notes on the notch's Kanban
+board and the Shelf. The [README](../README.md) shows the product; this is
+the inventory. Technical ids (`ruixen.bar`, `ruixen.launcher`, ...) are
+unchanged. Back to the [manual index](README.md).
+
+## What's included
+
+- **`ruixen.bar`** — the top bar itself: app launcher, dot-style
+ workspace indicator, pinned quick-launch apps, weather, clock, and a
+ settings shortcut, all in one connected pill layout. Fresh installs use
+ floating glass pills with accent-toned icons.
+- **`ruixen.notch`** — a center-notch dashboard with metrics, wallpapers
+ (and a theme switcher on the same tab: a segmented control above the
+ search box lists every installed Omarchy theme; clicking one applies it
+ via Omarchy's own `omarchy-theme-set`),
+ storage, music control, a notification history card (attaches to
+ Omarchy's own notification service, adding read/unread tracking and a
+ deeper backlog on top of it), and a Kanban board (see below), expanding
+ from the bar.
+- **`ruixen.launcher`** — a Raycast/Spotlight-style command palette in a
+ frosted-glass card (real Hyprland compositor blur, not a fake overlay):
+ fuzzy-searches Omarchy menu actions and installed apps from one overlay,
+ plus a dedicated Search Files mode. Searches both filenames (multi-word
+ queries match across path components, not just the final segment) and
+ file *contents* (ripgrep-powered, ranked together — content matches
+ fill in around real filename hits rather than needing a separate mode),
+ across every auto-discovered drive (an internal HDD, a USB stick)
+ individually or all at once — plus any custom folder you add yourself,
+ or exclude, from this plugin's own built-in Settings extension (its
+ File Search page). Filter by file
+ type/hidden-files/names-or-contents from a small control row, or type
+ the same filters directly into the query (`type:image`, `in:Home`,
+ `hidden:true`, ...). Selecting a file shows a real preview — an
+ extracted video frame, an image thumbnail, or a text/markdown/JSON
+ snippet — plus metadata (type, dimensions/duration, created/modified,
+ permissions), and a contextual action menu (Tab, or right-click a row)
+ for opening its containing folder or copying its path/name. It also hosts
+a Clipboard History view (see [`LAUNCHER.md`](LAUNCHER.md#clipboard-history))
+and a browser for the bjarneo community Omarchy themes. Its own
+ built-in Settings extension is also the full settings app for this
+ shell — Profile, Bar, Audio, Wi-Fi, Bluetooth, Display, Night Light,
+ Plugins — replacing the default Omarchy settings panel entirely. Full
+ reference: [`docs/LAUNCHER.md`](LAUNCHER.md).
+- **The screen frame** — the border that ties the bar and notch together
+ visually, in black or following the active theme. It is part of `ruixen.bar` (an earlier standalone
+ `ruixen.frame-widget` was merged into it).
+- **`ruixen.shelf`** — the drop pocket under the notch (see
+ [Shelf](#shelf-drop-pocket) below).
+- **`ruixen.wallpaper`** — muted looping video wallpaper support, chosen
+ from the notch's own Wallpapers picker; it sits alongside Omarchy's static
+ background rather than replacing it.
+- **`ruixen.cava`** — a live, edge-docked, audio-reactive spectrum overlay,
+ controlled from Ruixen Settings. Needs `cava` and PipeWire.
+- **`ruixen.workspaces`**, **`ruixen.power`**, **`ruixen.capturestatus`** —
+ bar widgets: a dots-and-pill workspace indicator, battery/power-profile/
+ system stats, and a pinned screen-recording control.
+- **`ruixen.pinnedapps`** — quick-launch row for apps pinned in the notch's
+ own app launcher.
+- **`ruixen.pluginpins`** — a pin/unpin dropdown on the bar for any other
+ installed bar-widget plugin (yours or a third party's) — install
+ something new, pin it from here, no shell.json editing required.
+- **`ruixen.peripherals`** — battery percentage for wireless mice,
+ keyboards, headsets and controllers (Bluetooth and USB receivers alike),
+ pin the ones you care about to show inline on the bar. Detection reads
+ `/sys` directly rather than Quickshell's own Bluetooth/UPower bindings,
+ which don't reliably cover every wireless peripheral — ported from
+ [xgborgeso/omarchy-peripheral-batteries](https://github.com/xgborgeso/omarchy-peripheral-batteries)
+ (MIT license).
+- **Tray widgets** — `ruixen.tray`, `ruixen.stayawake`,
+ `ruixen.quickactions`, `ruixen.weather`, `ruixen.applauncher`,
+ `ruixen.settingsbutton`. `ruixen.stayawake` (and any stock Omarchy widget
+ it sits next to, like the AI usage indicator) is pinned on or off through
+ `ruixen.pluginpins` above, not a separate settings toggle.
+
+`ruixen.media` backs `ruixen.notch`'s own music control as a background
+service — it never shows a bar icon of its own by design (an earlier,
+oversized play/pause badge was retired), so it's locked in Settings' Plugins
+list with no toggle.
+
+Every plugin shares the same surface (black or theme-aware, glass or solid),
+corner radii, and motion language, so they read as one shell instead of a pile of separate widgets.
+
+## Kanban board
+
+`ruixen.notch`'s dashboard has a 4th tab: a fixed 3-column board (Todo / In
+Progress / Done — Tab cycles through all 4 tabs, or click the column-icon in
+the left rail). It's agent-native — every mutation (add, move, rename,
+priority, due date, label, description) is a plain IPC call a script or
+agent can drive — and it's fully editable in the notch itself now too:
+per-column add buttons, hover edit/delete on each card, and a done/total
+progress bar. Renaming a column stays CLI-only. Full command reference and
+how the click model works: [`docs/CONTROL.md`](CONTROL.md).
+
+## Shelf (drop pocket)
+
+`ruixen.shelf` is a panel that grows out of the frame at the notch's position — the notch's expanded silhouette, with concave wing shoulders, hanging from the top edge: drag files in
+from any app, drag them back out into another app or a terminal. It
+remembers file paths, never copies anything. Drag local files over the
+collapsed notch and the Shelf opens so you can drop them in and see them land
+(it hides again if you drag back out without dropping). It's
+agent-readable too — `omarchy-shell ruixen.shelf list` shows an agent what
+you dropped, and `add /abs/path` lets it put a file on the shelf for you to
+drag out. It's its own plugin rather than a notch tab so other apps stay
+reachable for drag-and-drop. Details: [`docs/CONTROL.md`](CONTROL.md).
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..436751d
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,38 @@
+# Ruixen manual
+
+Everything beyond the [README](../README.md) lives here. Pick what you need:
+
+## Getting set up
+
+- **[Installation and maintenance](INSTALLATION.md)**: install, dry-run,
+ recovering from an interrupted run, updating, the doctor and repair
+ scripts, disabling plugins, going back to stock Omarchy, uninstalling,
+ and requirements.
+- **[Keybinds](KEYBINDS.md)**: ready-to-use Hyprland keybind recipes for the
+ Launcher, Settings, the Shelf, Clipboard History, the notch and the app
+ launcher.
+
+## Using Ruixen
+
+- **[Ruixen Launcher](LAUNCHER.md)**: the command palette in full:
+ applications and commands, Search Files (names and contents, filters,
+ query operators), the actions menu, Clipboard History, and choosing which
+ folders get searched.
+- **[Customizing](CUSTOMIZATION.md)**: docked or floating bar, the notch or
+ full-width bar style, and the Hyprland window look-and-feel.
+- **[Plugins and features](PLUGINS.md)**: what each plugin does, plus the
+ Kanban board and the Shelf drop pocket.
+
+## Automating Ruixen
+
+- **[Control from a script or an agent](CONTROL.md)**: every plugin is
+ controllable over `omarchy-shell `, with the Kanban board
+ and the Shelf as worked examples.
+
+## Elsewhere in the repository
+
+- [`COMPATIBILITY.md`](../COMPATIBILITY.md): which Omarchy and Quickshell
+ versions have been reviewed.
+- [`dev/README.md`](../dev/README.md): building, testing and contributing.
+- [`AGENTS.md`](../AGENTS.md): the repo-wide rules for coding agents and
+ humans editing Ruixen.
diff --git a/preview/preview_0.webp b/preview/preview_0.webp
new file mode 100644
index 0000000..d885d6a
Binary files /dev/null and b/preview/preview_0.webp differ
diff --git a/preview/preview_1.webp b/preview/preview_1.webp
new file mode 100644
index 0000000..56d854c
Binary files /dev/null and b/preview/preview_1.webp differ
diff --git a/preview/preview_2.webp b/preview/preview_2.webp
new file mode 100644
index 0000000..8c178e9
Binary files /dev/null and b/preview/preview_2.webp differ
diff --git a/preview/preview_clipboard.webp b/preview/preview_clipboard.webp
new file mode 100644
index 0000000..3b28601
Binary files /dev/null and b/preview/preview_clipboard.webp differ
diff --git a/preview/preview_dock.png b/preview/preview_dock.png
deleted file mode 100644
index de674b9..0000000
Binary files a/preview/preview_dock.png and /dev/null differ
diff --git a/preview/preview_dock.webp b/preview/preview_dock.webp
new file mode 100644
index 0000000..28164ce
Binary files /dev/null and b/preview/preview_dock.webp differ
diff --git a/preview/preview_float.png b/preview/preview_float.png
deleted file mode 100644
index 3eb20bb..0000000
Binary files a/preview/preview_float.png and /dev/null differ
diff --git a/preview/preview_float.webp b/preview/preview_float.webp
new file mode 100644
index 0000000..01245be
Binary files /dev/null and b/preview/preview_float.webp differ
diff --git a/preview/preview_kanban.webp b/preview/preview_kanban.webp
new file mode 100644
index 0000000..5a2a58f
Binary files /dev/null and b/preview/preview_kanban.webp differ
diff --git a/preview/preview_launcher.webp b/preview/preview_launcher.webp
index eab0439..d337c2d 100644
Binary files a/preview/preview_launcher.webp and b/preview/preview_launcher.webp differ
diff --git a/preview/preview_launcher_files.webp b/preview/preview_launcher_files.webp
new file mode 100644
index 0000000..dcaae6b
Binary files /dev/null and b/preview/preview_launcher_files.webp differ
diff --git a/preview/preview_notch.png b/preview/preview_notch.png
deleted file mode 100644
index af85b31..0000000
Binary files a/preview/preview_notch.png and /dev/null differ
diff --git a/preview/preview_notch.webp b/preview/preview_notch.webp
new file mode 100644
index 0000000..723cd0e
Binary files /dev/null and b/preview/preview_notch.webp differ
diff --git a/preview/preview_settings.png b/preview/preview_settings.png
deleted file mode 100644
index cd264b6..0000000
Binary files a/preview/preview_settings.png and /dev/null differ
diff --git a/preview/preview_settings.webp b/preview/preview_settings.webp
new file mode 100644
index 0000000..f4336d8
Binary files /dev/null and b/preview/preview_settings.webp differ
diff --git a/preview/preview_shelf.webp b/preview/preview_shelf.webp
new file mode 100644
index 0000000..51bf698
Binary files /dev/null and b/preview/preview_shelf.webp differ
diff --git a/preview/preview_themes.webp b/preview/preview_themes.webp
new file mode 100644
index 0000000..372e450
Binary files /dev/null and b/preview/preview_themes.webp differ
diff --git a/tests/bar-style-mode.sh b/tests/bar-style-mode.sh
index c2807b8..46a32cb 100755
--- a/tests/bar-style-mode.sh
+++ b/tests/bar-style-mode.sh
@@ -14,7 +14,7 @@ fullbar_dock_skin_qml="$repo_dir/bars/v2/ruixen.bar/FullbarDockedSkin.qml"
notch_qml="$repo_dir/bars/widgets/ruixen.notch/Overlay.qml"
barpanel_qml="$repo_dir/bars/v2/ruixen.bar/BarPanel.qml"
style_script="$repo_dir/dev/ruixen-bar-style.sh"
-readme="$repo_dir/README.md"
+readme="$repo_dir/docs/CUSTOMIZATION.md"
install_sh="$repo_dir/install.sh"
pass=0
@@ -81,7 +81,7 @@ check "bar style helper supports fullbar" \
check "bar style helper writes bar.style" \
"$(grep -m1 -F "d.setdefault('bar', {})['style'] = '\$style'" "$style_script")" "d.setdefault('bar', {})['style'] = '\$style'"
-check "README documents fullbar style" \
+check "CUSTOMIZATION.md documents fullbar style" \
"$(grep -m1 './dev/ruixen-bar-style.sh fullbar' "$readme")" './dev/ruixen-bar-style.sh fullbar # full-width statusline skin, no notch'
check "install output mentions fullbar helper" \
diff --git a/tests/docs-links.sh b/tests/docs-links.sh
new file mode 100755
index 0000000..2e61958
--- /dev/null
+++ b/tests/docs-links.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Documentation link check. The README, docs/ and dev/ README are a
+# three-layer manual that links between itself heavily; a moved or renamed
+# file silently breaks those links on GitHub. This resolves every relative
+# markdown link and image src in those files (file must exist, and a
+# `#fragment` must match a heading in the target) and fails on any that
+# does not. External http(s) links are not fetched.
+set -Eeuo pipefail
+
+repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$repo_dir"
+
+python3 - "$repo_dir" <<'PY'
+import os
+import re
+import sys
+
+root = sys.argv[1]
+files = ["README.md", "dev/README.md", "AGENTS.md", "COMPATIBILITY.md"]
+files += sorted("docs/" + f for f in os.listdir(os.path.join(root, "docs")) if f.endswith(".md"))
+
+link = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)|
]+src=\"([^\"]+)\"|]+href=\"([^\"]+)\"")
+
+
+def slug(heading):
+ s = heading.strip().lower()
+ s = re.sub(r"[^\w\s-]", "", s)
+ return re.sub(r"\s", "-", s)
+
+
+def anchors(path):
+ out = set()
+ in_code = False
+ for line in open(path, encoding="utf-8"):
+ if line.startswith("```"):
+ in_code = not in_code
+ elif not in_code and re.match(r"^#{1,6}\s", line):
+ out.add(slug(re.sub(r"^#{1,6}\s+", "", line)))
+ return out
+
+
+bad = 0
+checked = 0
+for f in files:
+ base = os.path.dirname(os.path.join(root, f))
+ in_code = False
+ for n, line in enumerate(open(os.path.join(root, f), encoding="utf-8"), 1):
+ if line.startswith("```"):
+ in_code = not in_code
+ continue
+ if in_code:
+ continue
+ for m in link.finditer(line):
+ target = next(g for g in m.groups() if g)
+ if re.match(r"^[a-z][a-z0-9+.-]*:", target):
+ continue
+ path, _, frag = target.partition("#")
+ dest = os.path.normpath(os.path.join(base, path)) if path else os.path.join(root, f)
+ checked += 1
+ if not os.path.exists(dest):
+ print(f"FAIL {f}:{n} -> {target} (no such file)")
+ bad += 1
+ elif frag and dest.endswith(".md") and frag not in anchors(dest):
+ print(f"FAIL {f}:{n} -> {target} (no such heading)")
+ bad += 1
+
+print(f"{checked - bad} passed, {bad} failed")
+sys.exit(1 if bad else 0)
+PY
diff --git a/tests/run-all.sh b/tests/run-all.sh
index e297e38..860636a 100755
--- a/tests/run-all.sh
+++ b/tests/run-all.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Runs every test in this directory and reports one final pass/fail
# summary -- what CI runs (.github/workflows/ci.yml) and what local
-# development should run before pushing. See README.md's own
+# development should run before pushing. See dev/README.md's own
# "Running tests" section for what each individual script covers.
set -Eeuo pipefail
@@ -19,6 +19,7 @@ suites=(
"$script_dir/curvature-half-option.sh"
"$script_dir/notch-kanban-gui.sh"
"$script_dir/shelf-plugin.sh"
+ "$script_dir/docs-links.sh"
"$script_dir/notch-theme-switcher.sh"
"$script_dir/glass-profile-contract.sh"
"$script_dir/glass-tint-contract.sh"