diff --git a/README.md b/README.md index 05473d4..ff1da18 100644 --- a/README.md +++ b/README.md @@ -4,52 +4,66 @@ [![Release](https://github.com/Roddygithub/discord-voice-overlay/workflows/Release/badge.svg)](https://github.com/Roddygithub/discord-voice-overlay/actions/workflows/release.yml) [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0) [![Version](https://img.shields.io/github/v/tag/Roddygithub/discord-voice-overlay?label=version&sort=semver)](https://github.com/Roddygithub/discord-voice-overlay/releases) +[![Downloads](https://img.shields.io/github/downloads/Roddygithub/discord-voice-overlay/total)](https://github.com/Roddygithub/discord-voice-overlay/releases) A native Wayland voice activity overlay for Discord Desktop and Vesktop on Linux. -Discord Voice Overlay shows who is speaking without changing the game process: -participant names and avatars, speaking and mute/deaf indicators, configurable -positioning, and click-through behavior. It includes a shared custom Vencord -plugin and the v1.3.0 universal installer/manager. - -## Overview - -A lightweight, highly responsive, **Wayland-native (layer-shell)** overlay that displays current voice channel participants and highlights active speakers in Discord Desktop or Vesktop. - -### Key Features - -- 🔒 **Privacy-first**: No Discord token access, no self-bots, no separate Gateway connections -- 🖥️ **Wayland-native**: Uses `layer-shell` protocol with an empty input region, so mouse clicks pass through to whatever is underneath (wlroots compositors: Hyprland, sway, niri, etc.) -- ⚡ **Low latency**: Event-driven voice updates over a local Unix socket -- 🎮 **Game compatible**: Click-through overlay works over fullscreen XWayland games -- 🔄 **Auto-reconnect**: Fast bounded backoff (≤ 2s) if Discord Desktop, Vesktop, or the overlay restarts; the latest settings and voice snapshot are replayed automatically so no voice activity is needed to repopulate the overlay -- 🚀 **Session autostart**: ships a `systemd --user` service (`vesktop-voice-overlay.service`) -- 🧰 **Universal manager**: v1.3.0 provides install, status, doctor, update, repair, and uninstall for supported native clients - -## Architecture - -``` -┌─────────────────┐ Unix Socket ($XDG_RUNTIME_DIR) ┌──────────────────┐ -│ Discord/Vesktop │ ──────────────────────────────────────► │ Overlay Client │ -│ (Vencord Plugin)│ ◄────────────────────────────────────── │ (Rust + GTK4) │ -└─────────────────┘ user-only (0700) └──────────────────┘ -``` - -| Component | Technology | Role | -|-----------|------------|------| -| **Vencord Plugin** | TypeScript / Node.js | Runs inside Discord Desktop or Vesktop, extracts voice state, sends JSON snapshots via Unix socket | -| **Overlay App** | Rust / GTK4 / layer-shell | Wayland click-through overlay, receives snapshots, renders avatars + speaking indicators | - -### Security & Privacy - -- ✅ **No Discord token** — Never reads, stores, or transmits your account token -- ✅ **No self-bots** — No separate Discord Gateway connections -- ✅ **Same-user IPC** — Unix domain socket under `$XDG_RUNTIME_DIR` with `0700` permissions + `SO_PEERCRED` UID validation -- ✅ **Minimal data boundary** — Only required voice-state snapshots cross the local socket; avatar images may be fetched from Discord's CDN over HTTPS +See who is speaking without touching the game process: participant names and +avatars, speaking and mute/deaf indicators, configurable position and monitor, +and native click-through. It is a shared custom Vencord plugin plus a Rust +overlay, installed and managed by the v1.3.0 universal installer. + + + +## Contents + +- [Highlights](#highlights) +- [How it compares](#how-it-compares) +- [Installation](#installation) +- [Usage](#usage) +- [Configuration](#configuration) +- [Auto-start (systemd user service)](#auto-start-systemd-user-service) +- [Manual build and development](#manual-build-and-development) +- [Architecture](#architecture) +- [Security and privacy](#security-and-privacy) +- [Socket protocol](#socket-protocol) +- [Supported compositors](#supported-compositors) +- [Distribution](#distribution) +- [Development](#development) +- [License](#license) + +## Highlights + +- 🔒 **Privacy-first** — no Discord token access, no self-bots, no separate Gateway connections +- 🖥️ **Wayland-native** — `layer-shell` with an empty input region, so mouse clicks pass through to whatever is underneath (wlroots compositors: Hyprland, sway, niri, etc.) +- 📺 **Multi-monitor** — pin the overlay to the primary monitor, the monitor under the cursor, or an explicit output +- ⚡ **Low latency** — event-driven voice updates over a local Unix socket +- 🎮 **Game compatible** — click-through overlay works over fullscreen XWayland games +- 🔄 **Auto-reconnect** — fast bounded backoff (≤ 2 s) if Discord Desktop, Vesktop, or the overlay restarts; the latest settings and voice snapshot are replayed automatically so no voice activity is needed to repopulate the overlay +- 🚀 **Session autostart** — ships a `systemd --user` service (`vesktop-voice-overlay.service`) +- 🧰 **Universal manager** — v1.3.0 provides `install`, `status`, `doctor`, `update`, `repair`, and `uninstall` for supported native clients + +## How it compares + +There are other Discord overlays for Linux. This one exists because it draws +through Wayland layer-shell rather than a normal window, and because it never +touches your account token. + +| | **Discord Voice Overlay** | [Overlayed](https://github.com/overlayeddev/overlayed) | [Discover](https://github.com/trigg/Discover) | [DiscordOverlayLinux](https://github.com/trigg/DiscordOverlayLinux) | +|---|---|---|---|---| +| Stack | Rust + GTK4 | TypeScript (Tauri) | Python + GTK3 | Python + Qt | +| Drawn as | layer-shell surface | desktop window | desktop window | X11 window | +| Compositors | Wayland (wlroots) | Win / macOS / Linux | X11 + wlroots | X11 | + +Pick **Overlayed** if you want a polished cross-platform app with a UI of its +own. Pick **Discover** if you want maximum configurability or run X11. Pick +this one if you want the overlay to sit in the compositor's layer stack, +pass clicks to the game underneath, and stay out of the way of your Discord +session. ## Installation -### Universal Installer (Arch Linux / Omarchy) +### Universal installer (Arch Linux / Omarchy) The primary v1.3.0 installation path manages the overlay and one native Vencord client without changing pacman packages or requiring root. It supports native @@ -68,37 +82,115 @@ enables only the managed plugin in Vencord settings, and preserves `~/.config/vesktop-voice-overlay/config.toml`. ```bash -./install.sh install -./install.sh status -./install.sh doctor -./install.sh update -./install.sh repair -./install.sh uninstall +./install.sh install # install or reinstall +./install.sh status # what is installed, who owns it +./install.sh doctor # diagnose client / service / ownership problems +./install.sh update # fetch and verify the latest release +./install.sh repair # rebuild the managed Vencord checkout +./install.sh uninstall # remove what the manager owns ``` +Release binaries and plugin source bundles are available on the +[`v1.3.0` release page](https://github.com/Roddygithub/discord-voice-overlay/releases/tag/v1.3.0); +the manager normally downloads and verifies the matching assets for you. + Flatpak, AppImage, arbitrary custom installations, and existing foreign custom -Vencord/injected setups are detected or rejected safely; see +Vencord/injected setups are detected or rejected safely — see [`docs/installer.md`](docs/installer.md). `--dry-run` shows changes without modifying client, service, or overlay state. -Release binaries and plugin source bundles are available on the -[`v1.3.0 release`](https://github.com/Roddygithub/discord-voice-overlay/releases/tag/v1.3.0) -page; the manager normally downloads and verifies the matching release assets -for you. - If an existing custom Vencord integration or injected Discord target is found, the manager refuses to overwrite or silently adopt it. This protects existing Vencord plugins and client modifications. Use `status` and `doctor` to inspect -ownership and supported-client problems; review [`docs/installer.md`](docs/installer.md) -before deciding whether to remove or reconfigure the foreign integration. +ownership and supported-client problems, and review +[`docs/installer.md`](docs/installer.md) before deciding whether to remove or +reconfigure the foreign integration. -### Manual Build and Development +## Usage -The following paths are for development, unsupported packaging variants, or -users who intentionally manage the client integration themselves. They are not -the primary v1.3.0 installation path. +1. Start the overlay: `systemctl --user start vesktop-voice-overlay` (or run + `./target/release/vesktop-voice-overlay` directly) +2. Open Discord Desktop or Vesktop and join a voice channel +3. The overlay appears automatically with participant avatars +4. **Green ring** = currently speaking (static highlight) + +Settings live in Vencord → Plugins → **VesktopVoice Overlay** (see +[Configuration](#configuration)); changes apply immediately. + +## Configuration + +Runtime behavior is driven by the plugin settings in Discord Desktop or +Vesktop. Changes apply immediately and are replayed automatically after any +restart. + +| Setting | Options | +|---|---| +| `enabled` | show the voice widget in games | +| `position` | top right (default), top left, bottom right, bottom left, center, custom coordinates | +| `customX` / `customY` | horizontal / vertical offset when position is `custom` | +| `userDisplay` | speaking only (default), always | +| `nameDisplay` | speaking only (default), always, never | +| `avatarSize` | small (default), large | +| `monitor` | primary (default), cursor monitor, monitor 0–3 | + +An optional TOML file at `~/.config/vesktop-voice-overlay/config.toml` is +read at overlay startup if present — it is **never created or written** by +the overlay. The `[socket]` path and `[overlay].max_participants` are durable +local options. Other overlay display values act as startup defaults and are +overridden when plugin settings arrive. Legacy `[appearance]` and +`overlay.avatar_size` keys are ignored: -#### Quick Start (Arch Linux / Hyprland) +```toml +[socket] +path = "/run/user/1000/vesktop-voice-overlay.sock" # default: $XDG_RUNTIME_DIR/vesktop-voice-overlay.sock +``` + +The plugin fails closed if `$XDG_RUNTIME_DIR` is unavailable. An explicit +overlay socket path is only useful for manual protocol clients because the +plugin always uses the runtime-directory path. + +### Why is the plugin called `VesktopVoiceOverlay`? + +`VesktopVoiceOverlay` is the historical internal Vencord plugin identifier. +Vencord uses it as part of plugin identity and persisted settings, so it is +intentionally retained for backward compatibility. The project itself is +Discord Voice Overlay and supports both Discord Desktop and Vesktop. + +## Auto-start (systemd user service) + +The packaging template installs `vesktop-voice-overlay.service` in +`/usr/lib/systemd/user/`. Once installed, it starts the overlay with your +graphical session, restarts it if it ever exits, and is independent of +Vesktop's lifecycle (the plugin reconnects whenever Vesktop appears). + +```bash +# Enable autostart for every session: +systemctl --user enable --now vesktop-voice-overlay.service + +# Manual control: +systemctl --user status vesktop-voice-overlay.service +journalctl --user -u vesktop-voice-overlay.service -f +``` + +If your compositor session does not activate `graphical-session.target` +(e.g. Hyprland started without uwsm), either start it from your Hyprland +config (`exec-once = systemctl --user start vesktop-voice-overlay`) or enable +the default.target variant: + +```bash +systemctl --user enable vesktop-voice-overlay.service +``` + +Running a second instance manually while the service owns the socket fails +cleanly with `another vesktop-voice-overlay instance owns ...` and exit code 1. + +## Manual build and development + +The paths below are for development, unsupported packaging variants, or users +who intentionally manage the client integration themselves. They are not the +primary v1.3.0 installation path. + +### Quick start (Arch Linux / Hyprland) ```bash # 1. Install build/runtime dependencies @@ -109,16 +201,12 @@ git clone https://github.com/Roddygithub/discord-voice-overlay.git cd discord-voice-overlay/overlay cargo build --release --locked -# 3. Build Vencord with the source userplugin (instructions below), then run +# 3. Build Vencord with the source userplugin (workflow below), then run ./target/release/vesktop-voice-overlay ``` -The repository contains an AUR `PKGBUILD`, but no package is currently -published in the AUR. - -#### Manual Build (Any Linux) +### Prerequisites -#### Prerequisites ```bash # Arch sudo pacman -S rust gtk4 libadwaita gtk4-layer-shell pkg-config @@ -128,7 +216,8 @@ sudo pacman -S rust gtk4 libadwaita gtk4-layer-shell pkg-config # https://github.com/wmww/gtk4-layer-shell) ``` -#### Build Overlay (Rust) +### Build the overlay (Rust) + ```bash git clone https://github.com/Roddygithub/discord-voice-overlay.git cd discord-voice-overlay/overlay @@ -136,7 +225,8 @@ cargo build --release --locked # Binary at: target/release/vesktop-voice-overlay ``` -#### Pack Plugin Source (optional) +### Pack the plugin source (optional) + ```bash cd ~/discord-voice-overlay/plugin npm ci @@ -148,7 +238,7 @@ The plugin is currently distributed as a Vencord source userplugin and must be included in a custom Vencord build. It has not been accepted into Vencord's built-in plugin set. -### Development: Vencord userplugin workflow (supported) +### Vencord userplugin workflow (supported) This is the workflow used for development and for custom Vencord client builds (this is how the plugin is actually built and loaded when using a local @@ -178,6 +268,7 @@ Verification: `grep -c VesktopVoiceOverlay dist/vencordDesktopRenderer.js` and `dist/vencordDesktopMain.js` must both be ≥ 1. ### Run + ```bash # Start overlay (keep running in background) ./target/release/vesktop-voice-overlay @@ -186,100 +277,107 @@ and `dist/vencordDesktopMain.js` must both be ≥ 1. RUST_LOG=debug ./target/release/vesktop-voice-overlay ``` -### Vencord Compatibility Note - -**Why does Vencord show `VesktopVoiceOverlay`?** +## Architecture -`VesktopVoiceOverlay` is the historical internal Vencord plugin identifier. -Vencord uses it as part of plugin identity and persisted settings, so it is -intentionally retained for backward compatibility. The project itself is -Discord Voice Overlay and supports both Discord Desktop and Vesktop. +``` +┌─────────────────┐ Unix Socket ($XDG_RUNTIME_DIR) ┌──────────────────┐ +│ Discord/Vesktop │ ──────────────────────────────────────► │ Overlay Client │ +│ (Vencord Plugin)│ ◄────────────────────────────────────── │ (Rust + GTK4) │ +└─────────────────┘ user-only (0700) └──────────────────┘ +``` -## Configuration +| Component | Technology | Role | +|-----------|------------|------| +| **Vencord Plugin** | TypeScript / Node.js | Runs inside Discord Desktop or Vesktop, extracts voice state, sends JSON snapshots via Unix socket | +| **Overlay App** | Rust / GTK4 / layer-shell | Wayland click-through overlay, receives snapshots, renders avatars + speaking indicators | -Runtime behavior is driven by the plugin settings in Discord Desktop or Vesktop -(Vencord plugin options: position, custom X/Y, user display, name display, -avatar size). Changes apply immediately and are replayed automatically after -any restart. +## Security and privacy -An optional TOML file at `~/.config/vesktop-voice-overlay/config.toml` is -read at overlay startup if present — it is **never created or written** by -the overlay. The `[socket]` path and `[overlay].max_participants` are durable -local options. Other overlay display values act as startup defaults and are -overridden when plugin settings arrive. Legacy `[appearance]` and -`overlay.avatar_size` keys are ignored: - -```toml -[socket] -path = "/run/user/1000/vesktop-voice-overlay.sock" # default: $XDG_RUNTIME_DIR/vesktop-voice-overlay.sock -``` +- ✅ **No Discord token** — never reads, stores, or transmits your account token +- ✅ **No self-bots** — no separate Discord Gateway connections +- ✅ **Same-user IPC** — Unix domain socket under `$XDG_RUNTIME_DIR` with `0700` permissions + `SO_PEERCRED` UID validation +- ✅ **Minimal data boundary** — only required voice-state snapshots cross the local socket; avatar images may be fetched from Discord's CDN over HTTPS -The plugin fails closed if `$XDG_RUNTIME_DIR` is unavailable. An explicit -overlay socket path is only useful for manual protocol clients because the -plugin always uses the runtime-directory path. +## Socket protocol -## Usage +See [`docs/protocol.md`](docs/protocol.md) for the full spec. -1. Start the overlay (`systemctl --user start vesktop-voice-overlay` or `./target/release/vesktop-voice-overlay`) -2. Open Discord Desktop or Vesktop and join a voice channel -3. Overlay appears automatically with participant avatars -4. **Green ring** = currently speaking (static highlight) +**Handshake:** -## Auto-start (systemd user service) +``` +Server sends: "VESKTOP_VOICE_OVERLAY/1.0\n" +Client validates, then sends JSON Lines snapshots +``` -The packaging template installs `vesktop-voice-overlay.service` in -`/usr/lib/systemd/user/`. Once installed, it starts the overlay with your graphical session, -restarts it if it ever exits, and is independent of Vesktop's lifecycle -(the plugin reconnects whenever Vesktop appears). +On voice-channel leave, the plugin sends `{"type":"clear"}` so stale rows are +removed immediately and the clear state is replayed after reconnects. -```bash -# Enable autostart for every session: -systemctl --user enable --now vesktop-voice-overlay.service +**Snapshot:** -# Manual control: -systemctl --user status vesktop-voice-overlay.service -journalctl --user -u vesktop-voice-overlay.service -f +```json +{ + "version": 1, + "timestamp": 1692000000000, + "self": { + "userId": "123...", + "username": "You", + "avatarUrl": "https://cdn.discordapp.com/...", + "mute": false, + "deaf": false, + "speaking": true + }, + "participants": [ + { "userId": "456...", "username": "Friend", "avatarUrl": "...", "speaking": false, "volume": 80 } + ] +} ``` -If your compositor session does not activate `graphical-session.target` -(e.g. Hyprland started without uwsm), either start it from your Hyprland -config (`exec-once = systemctl --user start vesktop-voice-overlay`) or enable -the default.target variant: +## Supported compositors -```bash -systemctl --user enable vesktop-voice-overlay.service -``` +Layer-shell support is compositor-dependent: -Running a second instance manually while the service owns the socket fails -cleanly with `another vesktop-voice-overlay instance owns ...` and exit code 1. +- ✅ **Hyprland** (primary target) — validated end-to-end on Hyprland 0.56 + with Guild Wars 2 (windowed/borderless): overlay visibility, pointer + click-through, game focus, speaking show/hide, avatar sizing +- ⚠️ **sway** / **niri** / **wayfire** — expected to work through layer-shell, + but not individually validated +- ⚠️ **GNOME / KDE** (layer-shell support varies) — untested +- ⚠️ **Exclusive-fullscreen** games — untested; multi-monitor placement is + configurable (see the `monitor` setting) but has not been validated across a + real multi-head setup ## Distribution -| Component | Channel | Install Command | +| Component | Channel | Install command | |-----------|---------|-----------------| | **Overlay (Rust)** | Source build; unpublished AUR template | Build with Cargo | | **Plugin (TypeScript)** | Source userplugin / GitHub source bundle | Build inside pinned Vencord source | -Both components versioned together via Git tags (`v1.0.0`, `v1.1.0`, etc.) — matching plugin + overlay share compatible socket protocol. +Both components are versioned together via Git tags (`v1.0.0`, `v1.1.0`, …) — +a matching plugin + overlay share a compatible socket protocol. + +A `PKGBUILD` lives in [`packaging/aur/`](packaging/aur/), but **no package is +currently published in the AUR**; build from source in the meantime. ## Development -### Project Structure +### Project structure + ``` -vesktop-voice-overlay/ -├── plugin/ # Vencord Plugin (TypeScript) +discord-voice-overlay/ +├── plugin/ # Vencord plugin (TypeScript) │ ├── src/ -│ │ ├── index.ts # Plugin entry point +│ │ ├── index.ts # Plugin entry point + settings │ │ ├── protocol.ts # Socket protocol types + serialization │ │ ├── native.ts # Node.js socket client (main process, net) │ │ ├── resendCache.ts # Reconnect backoff + settings/snapshot replay │ │ └── voiceState.ts # Vencord voice state accessors │ ├── package.json │ └── tsconfig.json -├── overlay/ # Overlay App (Rust) +├── overlay/ # Overlay app (Rust) │ ├── src/ │ │ ├── main.rs # GTK4 app entry -│ │ ├── layer_shell.rs # Wayland layer-shell setup +│ │ ├── layer_shell.rs # Wayland layer-shell setup + monitor resolution │ │ ├── socket_server.rs # Unix socket server + SO_PEERCRED │ │ ├── lifecycle.rs # Overlay show/hide logic │ │ ├── protocol.rs # Protocol deserialization @@ -289,12 +387,14 @@ vesktop-voice-overlay/ ├── packaging/aur/ # AUR PKGBUILD ├── memory-bank/ # Engineering docs (PRD, Tech Stack, Plan) ├── docs/protocol.md # Socket protocol v1 spec +├── docs/installer.md # Universal installer reference └── .github/workflows/ # CI/CD pipelines ``` -### CI/CD Pipeline -- **CI** (`.github/workflows/ci.yml`): Format check, build, test for both components -- **Release** (`.github/workflows/release.yml`): Tag push → validates and builds +### CI/CD pipeline + +- **CI** (`.github/workflows/ci.yml`) — format check, build, test for both components +- **Release** (`.github/workflows/release.yml`) — tag push → validates and builds artifacts → GitHub Release → optional AUR update when credentials are present ```bash @@ -303,65 +403,27 @@ cd overlay && cargo fmt --check && cargo clippy --all-targets --all-features -- cd ../plugin && npm run lint && npm test ``` -### Socket Protocol v1 -See [`docs/protocol.md`](docs/protocol.md) for full spec. - -**Handshake:** -``` -Server sends: "VESKTOP_VOICE_OVERLAY/1.0\n" -Client validates, then sends JSON Lines snapshots -``` - -On voice-channel leave, the plugin sends `{"type":"clear"}` so stale rows are -removed immediately and the clear state is replayed after reconnects. - -**Snapshot:** -```json -{ - "version": 1, - "timestamp": 1692000000000, - "self": { - "userId": "123...", - "username": "You", - "avatarUrl": "https://cdn.discordapp.com/...", - "mute": false, - "deaf": false, - "speaking": true - }, - "participants": [ - { "userId": "456...", "username": "Friend", "avatarUrl": "...", "speaking": false, "volume": 80 } - ] -} -``` - -## Supported Compositors - -Layer-shell support is compositor-dependent: -- ✅ **Hyprland** (primary target) — validated end-to-end on Hyprland 0.56 - with Guild Wars 2 (windowed/borderless): overlay visibility, pointer - click-through, game focus, speaking show/hide, avatar sizing -- ⚠️ **sway** / **niri** / **wayfire** — expected to work through layer-shell, - but not individually validated -- ⚠️ GNOME/KDE (layer-shell support varies) — untested -- ⚠️ Multi-monitor placement and exclusive-fullscreen games are untested - ## License -GPL-3.0 — See [LICENSE](LICENSE) +GPL-3.0 — see [LICENSE](LICENSE). Compatible with upstream projects: -- [Discover Overlay](https://github.com/trigg/Discover) (GPL-3.0) — Design inspiration -- [Vesktop](https://github.com/Vencord/Vesktop) (GPL-3.0) — Supported client -- [Discord](https://discord.com/) — Supported client -- [Vencord](https://github.com/Vendicated/Vencord) (GPL-3.0) — Plugin platform + +- [Discover Overlay](https://github.com/trigg/Discover) (GPL-3.0) — design inspiration +- [Vesktop](https://github.com/Vencord/Vesktop) (GPL-3.0) — supported client +- [Discord](https://discord.com/) — supported client +- [Vencord](https://github.com/Vendicated/Vencord) (GPL-3.0) — plugin platform ## Disclaimer -> This project is not affiliated with Discord, Vesktop, Vencord, or Discover Overlay. Client modifications may violate Discord's Terms of Service; use at your own risk. +> This project is not affiliated with Discord, Vesktop, Vencord, or Discover +> Overlay. Client modifications may violate Discord's Terms of Service; use at +> your own risk. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Significant architecture +or protocol changes should be discussed in an issue first. --- diff --git a/memory-bank/progress.md b/memory-bank/progress.md index c28fc40..c2fdcc6 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -734,3 +734,28 @@ Overlay can now target a specific monitor instead of the compositor's default. - Plugin (`plugin/src/index.ts`): new `monitor` SELECT setting (primary / cursor / index 0-3) sent in `sendSettings()`. - Gates: cargo fmt/clippy/test(40)/build, plugin eslint/vitest(25) — all green. + +## README rewrite — 2026-10-02 + +Documentation-only restructure of `README.md` (243 insertions, 181 deletions). +No product or code change. + +- Reordered for a visitor: intro → highlights → comparison → install → + usage → configuration → systemd → manual build → architecture/protocol. + Previously installation (100 lines of build) came before usage, and + `Overview` restated the intro nearly verbatim. +- Added a `Contents` anchor list; all 15 links validated against GitHub's + heading slugs (rendered through the GFM API: 4 tables, no broken anchors). +- New `How it compares` section with verified facts on Overlayed (Tauri), + Discover (Python/GTK3, X11 + wlroots), DiscordOverlayLinux (Python/Qt, X11) + pulled from their own READMEs/metadata — no unverifiable feature claims. +- Added a `Downloads` shields badge; social preview image is still unset. +- Fixed stale content: plugin settings table now documents `monitor` + (missing after #26), project-structure root renamed to + `discord-voice-overlay/`, multi-monitor compositor note now says + configurable-but-unvalidated, `layer_shell.rs` entry notes monitor + resolution, `docs/installer.md` added to the tree. +- Moved the "not published in the AUR" caveat out of the quick start and + into `Distribution`. +- Hero screenshot/GIF deliberately deferred — placeholder left as an HTML + comment so the top of the README stays clean until assets exist.