Metadata-Version: 2.5
Name: monocoat
Version: 1.2.0
Summary: One TOML, every Linux theming surface — GTK 2/3/4, Qt/Kvantum, libadwaita, window manager, notifications, cursor, fonts, terminals, wallpaper. Idempotent, drift-detecting, DE-agnostic.
Project-URL: Homepage, https://github.com/blakebratcher/monocoat
Project-URL: Repository, https://github.com/blakebratcher/monocoat
Project-URL: Issues, https://github.com/blakebratcher/monocoat/issues
Author: Blake Bratcher
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Desktop Environment
Classifier: Topic :: System :: Installation/Setup
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff==0.16.10; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# monocoat

**One TOML, every Linux theming surface.** monocoat (formerly archstylix) drives
GTK 2/3/4, xsettings, the window manager, Qt/Kvantum, notifications, the cursor
(optionally generated from the palette), fonts, colors, the dock, the launcher,
the panel, the wallpaper (optionally palette-following via `monocoat watch`),
Firefox chrome, a picom effects stack (CRT shaders, client and frame glass) and
an xrdp overlay from one file: `~/.config/monocoat/monocoat.toml`.

The TOML is canon. Every layer is re-derived from it on each `monocoat apply`,
so settings that a GUI dialog changed behind your back show up as drift and get
overwritten. Applying twice is a no-op: a layer whose live value already matches
reports "unchanged" and writes nothing.

See [TODO.md](TODO.md) for the current prioritized backlog, audit findings,
acceptance gates and a rough project-readiness assessment.
The [state-role and ownership reference](docs/state-roles.md) documents Kitty
include precedence, static doctor coverage, cross-consumer interaction roles
and the retained opaque-frame/glass-client policy.

## Why

A Linux desktop keeps its look in a dozen independent places: xfconf channels,
gsettings keys, `settings.ini` files, Kvantum and qt5ct/qt6ct configs, rofi
`.rasi` files, polybar templates, notification CSS. GUI dialogs each write a
few of them. Drift is the normal state. monocoat writes all of them from one
description and can tell you which ones have drifted since.

## Install

From a clone, no install step (the CLI is stdlib-only):

```bash
git clone https://github.com/blakebratcher/monocoat.git ~/src/monocoat
ln -s ~/src/monocoat/bin/monocoat ~/.local/bin/monocoat
```

`bin/monocoat` adds the repo's `src/` to `sys.path` and calls `monocoat.cli:main`,
so there is no pip, venv or PEP 668 step.

As a package, from the same clone:

```bash
cd ~/src/monocoat
pipx install .
```

monocoat is not on PyPI yet, so `pipx install monocoat` does not work. Install
from a clone until a release is published there.

Requires Python 3.11 or newer. Four optional tools are needed only by the
features that use them: ImageMagick (`magick`) for `monocoat generate`,
`rsvg-convert` (librsvg) for the palette-following cursor
(`[appearance].cursor_source`), `sassc` for `deep_recolor`'s SCSS-bound
assets, and pywal (`wal`) for the panel layer's polybar template re-render.
Everything else runs on the standard library alone.

## Quickstart

```bash
monocoat init                # write a starter ~/.config/monocoat/monocoat.toml
# or
monocoat init --from-live    # seed it from the desktop you're running now (XFCE)

monocoat apply --dry-run     # preview every change; nothing is written
monocoat apply               # write it
```

`init --from-live` followed by `apply` is designed to be a no-op: the config
describes what is already live.

For a fuller starting point, copy [`examples/monocoat.toml`](examples/monocoat.toml).
It uses only stock assets (Adwaita, xfwm4's Default theme, fontconfig's
Sans/Monospace) and documents the optional sections. Reserved settings are
marked as having no effect rather than presented as working knobs:

```bash
mkdir -p ~/.config/monocoat
cp examples/monocoat.toml ~/.config/monocoat/monocoat.toml
monocoat apply --dry-run
```

If there is no config, every command that needs one exits 3 and tells you to
run `monocoat init` (or `monocoat migrate`, if an archstylix config exists).

## Commands

| Command | What it does |
|---|---|
| `monocoat apply` | Apply the config to every layer, then reload only the daemons whose layers wrote something. |
| `monocoat apply --dry-run` | Show what apply would change. Writes nothing. |
| `monocoat apply --only L1,L2` / `--skip L1,L2` | Run only (or all but) the named layers. |
| `monocoat update [--setup | --check]` | Configure source-install updates once, check, or update code, managed themes and helpers together. Preserves local choices; verifies the result. See [Updating](docs/update.md). |
| `monocoat status` | Live state of every layer. |
| `monocoat diff [PRESET]` | Drift between the config (or a named preset) and the live desktop. |
| `monocoat doctor` | Diagnose the session: desktop, display server, backends, DPI, every probed tool (present or missing), daemons (including the resident dock's unit and lock), the active preset's validity, and which layers can run or are degraded. REFRESH names the last daemon refresh, when it ran and who asked for it (a terminal, the Settings window, the wallpaper follower, an update); `--json` carries it as `last_refresh`. Read-only X11 rows report the Composite/RANDR/... extensions, the compositor and tray selection owners, RandR monitors and the dock strut; TRAY reports the StatusNotifier watcher; PICOM checks the probed picom against `[effects.picom]` and Liquid Glass. `-v` adds each probe's command and raw output, and names (then frees) one window-frame pixmap without reading pixels. |
| `monocoat init [--from-live] [--force] [--name STEM] [--title TEXT] [--json]` | Create a starter config as `presets/STEM.toml` (default `default`), titled TEXT, and make it active. An existing preset of that name or a working config is refused without `--force`; a `monocoat.toml` link whose preset is gone counts as no config. `--json` prints `{preset, name, title}`. |
| `monocoat migrate [--dry-run] [--json]` | Move an archstylix config to `~/.config/monocoat/` and convert it to schema v2. A look whose file name `set` can't take is migrated under a valid one ("My Theme" → "My-Theme"); looks left behind are named. `--json` prints one document: the looks `moved`, `renamed` and `dropped`, the `active` one, the dropped keys and the exit code. |
| `monocoat list` | List presets; `*` marks the active one. |
| `monocoat set NAME [--pin]` | Make `presets/NAME.toml` the active preset and apply it. `--pin` pins it against `monocoat watch` switching; any manual `set` clears the pin. |
| `monocoat save NAME [--from-live]` | Save the active config (or the live desktop) as `presets/NAME.toml`. |
| `monocoat generate IMAGE --name NAME [--base B] [--force] [--strength 0..1] [--anchor H]` | Write a preset whose palette comes from a wallpaper ([adaptive palettes](docs/adaptive-palette.md)); never applies. Needs ImageMagick (`magick`). |
| `monocoat watch [--force]` | Follow the wallpaper: generate and switch presets as it changes (opt-in `[wallpaper].auto_switch`; XFCE only; passive until the adaptive coverage gate passes — `--force` bypasses the gate for dev/testing). |
| `monocoat verify --surface NAME:WINDOW_ID:X:Y …` | Opt-in X11 pixel measurement of glass in selected empty regions (at least two surfaces). See [Glass, frames, and pixel acceptance](#glass-frames-and-pixel-acceptance). |
| `monocoat edit [NAME]` | Open a preset in `$EDITOR`, validate it, and re-apply if it is the active one. |
| `monocoat reload [--json]` | Restart the managed daemons (polybar, the dock unit, picom), refresh the session services and running GTK3 apps, and re-run `[daemons].palette_hooks` — without applying. |
| `monocoat lock [--check]` | Lock an X11 session with the macOS-style wallpaper, clock, account photo and password field. `--check` is read-only; the [setup guide](docs/lockscreen.md) covers the native i3lock-color backend and optional suspend guard. |
| `monocoat notify-test` | Send one sample notification through `notify-send`, drawn by the desktop's own notification daemon in the current look. Exit 5 when `notify-send` is missing (Debian `libnotify-bin`, Arch `libnotify`), 4 when it fails. |
| `monocoat daynight [--next \| --oncalendar]` | Read-only: the light/dark scheme the next apply resolves (`[appearance].color_scheme`, `[appearance.schedule]`, `prefer_dark`, or the desktop's own preference for `system`) and the schedule's next boundary. `--next` prints that boundary; `--oncalendar` prints a drop-in for the documented [`monocoat-daynight.timer`](docs/monocoat-daynight.timer), which re-applies at sunrise and sunset. |
| `monocoat dock [--check]` | Run the resident monocoat dock (`[dock].backend = "monocoat"`; needs the system PyGObject + pycairo), or report its resolved config, icons, palette-token and font fallbacks, compositor, window-pid source (X-Resource), blur allowlist and LauncherEntry session bus without starting a GUI. |
| `monocoat import-mydockfinder PATH --out DIR [--name N] [--dry-run] [--force]` | Convert a MyDockFinder `config.ini`/`ico.ini` into a preset fragment in `DIR` with a mapped/unmapped/rejected report; offline, never applies, never copies MyDockFinder art, fonts, sounds, translations or account data ([guide](docs/THEME-DESIGNER-GUIDE.md#importing-a-mydockfinder-setup)). |
| `monocoat dock --pin ID [--at N]` / `--unpin ID` / `--move ID N` | Pin, unpin or reorder (0-based) a `.desktop` id in the machine-wide `~/.config/monocoat/dock.toml`, keeping comments: every look shows the same row and no preset is written (a preset's `[dock.daemon].pinned` is only the starting row). The running dock is restarted once so it shows the edit (`--from-dock`: the dock made the edit itself, so nothing restarts). See [the guide](docs/THEME-DESIGNER-GUIDE.md#pinning-and-reordering-write-docktoml). |
| `monocoat dock --folder-view ID [--stack MODE] [--sort ORDER] [--reverse true\|false]` | Give a pinned folder its own stack view in `dock.toml` (`default` follows the look's `folder_*` settings again); the dock's right-click Sort By / View Content As runs it. |
| `monocoat dock --message-app ID [--wm-class NAME] [--notify on\|off] [--banner true\|false\|default] [--seconds N\|default]` | List or unlist an app for message tips, or give it its own banner (`false`: the dock pill only) and banner time, in `dock.toml`'s `message_apps`; the dock's Notify for this app and the Settings window's message list write the same entries. |
| `monocoat launcher-index [--dry-run]` | Write shadow copies of the installed apps whose displayed name is Chinese, with pinyin search keys added to `Keywords=`, under `$XDG_CACHE_HOME/monocoat/launcher/applications/`. Start rofi with that root first in `XDG_DATA_DIRS` to search them; your own `~/.local/share/applications` files are never copied and always win. Rerun after installing apps. |
| `monocoat backup create [--label T]` / `list` / `restore NAME [--dry-run] [--confirm] [--keep-commands]` / `export NAME PATH` / `import PATH` / `delete NAME` | Back up every look (presets, bundles, recolor state, `apps.toml`) into one archive and restore it exactly, after an automatic "Before restore" backup; a backup from elsewhere has its commands reset unless kept. See [backups](docs/backups.md). |
| `monocoat wallpaper set [IMG] [--display OUTPUT] [--state FILE] [--json]` | Show IMG as the live desktop picture on every connected display (or one output's), without writing a preset (XFCE only, else exit 5); `--json` prints each slot's previous picture, which `--state` puts back exactly. |
| `monocoat wallpaper displays [--json]` | Read-only: the connected outputs in RandR order, their EDID model name, which is primary, and the picture each shows (under Span, the one display whose picture covers every screen; the others' pictures are marked hidden). |
| `monocoat settings` | The Settings window (GTK3, the system PyGObject): looks, colours, fonts, wallpaper, glass and corners, pointer and icons, dock, notifications, app coverage, health and backups, edited live with Ctrl+Z undo. It writes the active look's files and changes the desktop only through this CLI. |
| `monocoat demo shots` / `demo flip` | Capture a screenshot set / drive a preset round-trip for a video. |
| `monocoat` | Opens the Settings window, the same as `monocoat settings`; with no display it prints this help (exit 2). |

`apply`, `status`, `diff`, `doctor`, `daynight`, `dock`, `generate`, `backup`,
`import-mydockfinder`, `launcher-index` and `verify` also take `-q`, `-v` and `--json`.


### Exit codes

| Code | Meaning |
|---|---|
| 0 | Success. On a read-only command: clean, no drift. |
| 1 | `diff` or `apply --dry-run` found drift; `doctor` found a degraded but usable session. |
| 2 | Usage error, or a bare `monocoat` with no display to open the Settings window on. |
| 3 | Config error: missing config, invalid TOML, a failed schema or preset validation. Also `doctor` on an unusable session. |
| 4 | Partial failure: a layer raised, or a write was rejected (`WRITE FAILED`). |
| 5 | No usable settings backend for this session. |

## Presets

Keep several themes side by side in `~/.config/monocoat/presets/`. The active
one is whatever `~/.config/monocoat/monocoat.toml` symlinks to.

```
~/.config/monocoat/
├── monocoat.toml -> presets/tahoe-dark.toml
└── presets/
    ├── tahoe-dark.toml
    ├── tahoe-light.toml
    └── reference-amber.toml
```

```bash
monocoat list                 # '*' marks the active preset
monocoat diff tahoe-light     # what switching would change
monocoat set tahoe-light      # retarget the symlink and apply
monocoat set sea --pin        # retarget + pin against `monocoat watch` switching
monocoat save tahoe-backup    # snapshot the active config as a new preset
monocoat edit                 # edit the active preset; re-applies on save
```

`monocoat watch` (opt-in `[wallpaper].auto_switch`) generates and switches
among presets as the wallpaper changes, honouring a `set --pin` pin; it never
deletes presets. See [adaptive palettes](docs/adaptive-palette.md).

Theme assets a preset needs (picom shaders, polybar configs, scripts) live in
`~/.local/share/monocoat/themes/<preset>/` by default; set `[paths].assets_dir`
to put them elsewhere.

## Layers

`monocoat apply` runs these in order (see `src/monocoat/registry.py`):

| # | Layer | What it manages |
|---|---|---|
| 1 | `bundle` | Links the active asset bundle — theme dirs under their installed names and fixed-path includes, per `monocoat-bindings.toml` `[[link]]` entries — before any layer publishes a theme name. Only symlinks are ever replaced. |
| 2 | `gtk` | `~/.gtkrc-2.0`, `~/.config/gtk-3.0/settings.ini`, `~/.config/gtk-4.0/settings.ini` (prefer-dark follows the resolved light/dark scheme) |
| 3 | `settings` | Theme, icons, cursor, fonts and DPI broadcast to running apps: xfconf on XFCE, gsettings on GNOME, kdeglobals on KDE, xsettingsd on X11 window managers |
| 4 | `portal` | `color-scheme` and `accent-color` for the XDG desktop portal, so Flatpak, Electron and Firefox follow dark/light (opt-in: `[portal].enabled`) |
| 5 | `cursor` | `XCURSOR_*` in `environment.d`, an Xresources file, `~/.icons/default/index.theme` and the GNOME cursor keys; with `[appearance].cursor_source` it also builds the palette-following cursor theme (needs `rsvg-convert`) and refreshes the live session |
| 6 | `window_manager` | Decoration theme and title font (xfwm4; KWin on KDE) |
| 7 | `deep_recolor` | Palette changes carried into assets the preset's `monocoat-bindings.toml` claims — SCSS (compiled with `sassc`), INI keys, raster pixels/`#hex` literals (PNG/XPM/SVG) — plus unclaimed GTK CSS/Kvantum SVGs; reports assets edited since the last recolor |
| 8 | `qt` | `Kvantum/kvantum.kvconfig` (the `kvantum_theme_dark`/`_light` variant for the resolved scheme, when declared) and the style/icon lines of `qt5ct.conf`/`qt6ct.conf`; deploys the Kvantum theme's KDE color scheme |
| 9 | `notifications` | Notification styling: xfce4-notifyd theme, position and timeout; dunst and mako drop-ins; Plasma notifications |
| 10 | `dock` | Plank theme, per dock, through gsettings |
| 11 | `dockd` | The resident monocoat dock's `<cache_dir>/dock.json` snapshot: pinned `.desktop` ids and separators, icon sizes, reflection/indicator knobs, click/scroll actions, attention and badge settings, launch animation and tooltips, the `[fonts].ui` family and size, palette colours and glass byte. Active only with `[dock].backend = "monocoat"`; a write restarts `[daemons].dock_service` once |
| 12 | `launcher` | The `@theme` line in `~/.config/rofi/config.rasi`; the optional `[launcher.grid]` block (a paged icon grid) at the end of the theme rasi; with `[appearance].corner_radius`, a marked corner block in the theme rasi and `mc-radius*` variables in `shared/colors.rasi`; re-points a bundle menu's stale `@import` of another bundle's shell |
| 13 | `palette` | `[palette]` color tokens (and the `[appearance].corner_radius` notifyd radii) rewritten into rofi, notifyd CSS, kitty, polybar, conky and xfwm4 theme files, the bundle's own user `gtk.css` (bundle-owned files only), the `palette.env` cache export that drives `[daemons].palette_hooks`, and generated bundles' Firefox CSS |
| 14 | `fonts` | One font family pushed into polybar, conky, notifyd, rofi, kitty, Qt and the gsettings font keys |
| 15 | `libadwaita` | Palette-driven `@define-color` block in `gtk-4.0/gtk.css` for libadwaita/GTK4 apps (opt-in; refuses a symlinked `gtk.css` unless `follow_symlinks = true`) |
| 16 | `wallpaper` | `[wallpaper].path` and `mode`: every xfdesktop backdrop (XFCE), `org.gnome.desktop.background` (GNOME), `hyprpaper.conf` (Hyprland), a sway `output * bg` include (sway). `monocoat watch` (opt-in `[wallpaper].auto_switch`) switches presets as the wallpaper changes |
| 17 | `panel` | Re-renders the pywal polybar template when the palette changed |
| 18 | `effects` | picom posture, the CRT shader knobs, and `[effects].glass` across every glass surface — kitty, rofi, polybar `bg-glass`, GTK CSS, Kvantum, picom `frame-opacity`/`blur-background-frame` for titlebars, the Firefox chrome block (needs a preset asset bundle), enabled Alacritty/Foot opacity, and the `[appearance].corner_radius` picom corner rules |
| 19 | `remote` | Mirrors the GTK settings into `~/.config-xrdp/` and writes `xsettingsd.conf` for an xrdp session |

On an X11 window manager using xsettingsd, local and remote settings have
separate broker files: local uses `$XDG_CONFIG_HOME/xsettingsd/xsettingsd.conf`,
and remote uses `[remote].config_home/xsettingsd/xsettingsd.conf`. Start the
xrdp broker with the remote file (for example `xsettingsd -c
"$XDG_CONFIG_HOME/xsettingsd/xsettingsd.conf"` when its XDG config home is the
remote overlay). A remote `xsettingsd` symlink that resolves to the local broker
is rejected with exit 4; replace that shared layout with an independent directory
before applying. On XFCE, the existing local-file/remote-symlink layout is kept
because xfconf owns the local settings broadcast.

A section that is absent from the config leaves its surface alone. To turn a
layer off explicitly, set `[layers].<name> = false`.

## Desktop support

| Session | Status |
|---|---|
| XFCE on X11 | Full. Every layer is implemented and tested here. |
| xrdp (openbox + xsettingsd) | Supported through the `remote` overlay layer. |
| GNOME | Wired: gsettings settings, cursor, fonts, wallpaper, portal and libadwaita. Covered by sandboxed tests; not yet exercised on a live GNOME session. |
| KDE Plasma | Wired: kdeglobals/kcminputrc settings, KWin decoration, Plasma notifications, portal accent, KWin reconfigure on reload. Wallpaper is n/a. Sandbox-tested only. |
| Bare window managers (i3, sway, Hyprland, ...) | Wired: xsettingsd on X11 (HUP on change), gsettings on Wayland, sway/Hyprland wallpaper, dunst/mako notifications. Sandbox-tested only. |

The file-backed layers (GTK files, cursor, Qt, rofi and so on) don't depend on
the desktop. Run `monocoat doctor` to see which layers your session can run
and why the others can't.

## Migrating from archstylix

monocoat is the renamed archstylix. The old `theme` command and `bin/theme`
shim are gone. Use `monocoat` (the subcommands are the same).

The old config location `~/.config/theme/theme.toml` (and its `presets/`) still
works: monocoat reads it and prints a notice on every run. To move it:

```bash
monocoat migrate --dry-run    # show the plan: files moved, keys renamed or dropped
monocoat migrate              # move ~/.config/theme → ~/.config/monocoat, rewrite presets to schema v2
monocoat apply --dry-run      # a migrated XFCE preset should report no changes
```

The v1 layout (`[gtk]`, `[xfwm4]`, `[plank]`, `[rofi]`, `[polybar]`, `[xrdp]`,
`[display]`) becomes v2 (`[appearance]`, `[window_manager]`, `[dock]`,
`[launcher]`, `[panel]`, `[remote]`, `[scaling]`). See
[`examples/theme.toml`](examples/theme.toml) (legacy v1) next to
[`examples/monocoat.toml`](examples/monocoat.toml) (v2).

## Design notes

- **Idempotent.** Every write is read-compare-write. A clean system reports
  everything unchanged, and `apply --dry-run` exits 0.
- **Drift is visible.** `monocoat diff` exits 1 when something moved, so it
  works in scripts and login hooks.
- **One owner for daemon restarts.** Layers only write configuration.
  `daemons.reload_daemons()` restarts polybar, the dock unit and picom, and
  runs `[daemons].palette_hooks` and the xfwm4/GTK theme round-trips, and
  only for writes their layers actually made. Restarting from inside layers
  once stacked two polybars and crashed Plank.
- **Never signals xfsettingsd.** It has no SIGHUP handler, so a HUP kills it,
  and xfconf writes reach it live anyway.
- **Stdlib-only CLI.** PyGObject and pycairo are imported only by the Settings window and the dock.

## Project layout

```
monocoat/
├── bin/monocoat              # no-install shim (sys.path → src/, calls cli.main)
├── examples/
│   ├── monocoat.toml         # v2 example, stock assets only
│   └── theme.toml            # legacy v1 sample (and test fixture)
├── src/monocoat/
│   ├── cli.py                # argparse, apply_cfg (the apply choke point)
│   ├── config.py             # config location + load/validate
│   ├── schema.py             # v2 schema, defaults, v1 → v2 upgrade
│   ├── registry.py           # ordered layer registry + capability gates
│   ├── capabilities.py       # what a session can do
│   ├── session/              # desktop/display detection, backends
│   ├── layers/               # one module per theming surface
│   ├── diff.py               # read-only apply → status / diff rows
│   ├── doctor.py             # monocoat doctor
│   ├── daemons.py            # the single-owner reload pass
│   ├── commands/             # init, migrate, demo
│   └── gui/                  # the Settings window (GTK3)
└── tests/
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md): the layer protocol, the reload rule,
the write helpers, and the sandbox rule every test follows.

## License

MIT. See [LICENSE](LICENSE). Vendored data keeps its own license: the
Hackneyed cursor SVGs (MIT, `src/monocoat/data/hackneyed`) and the Han reading
table derived from the Unicode Unihan database (Unicode License v3,
`src/monocoat/data/unihan`).

## Glass, frames, and pixel acceptance

See [docs/schema.md](docs/schema.md) for every canonical key/default and the
ownership boundaries. `[effects].glass` (0–1) resolves to one alpha byte and
drives every glass surface: kitty's `background_opacity`, rofi's
`background-glass`, polybar's `bg-glass`, `alpha(<bg>, …)` in the GTK CSS, the
Kvantum window/menu interiors, picom's global `frame-opacity` plus
`blur-background-frame` (xfwm4 titlebars and borders join the glass), and the
managed block in the bundle's `firefox/userChrome.css` (restart Firefox; the
`#browser` rule extends the glass to the vertical-tabs sidebar). `0`
leaves it unmanaged. Compiled GTK CSS and its SCSS sources stay in step.
Toolbar `bg_alt` remains a distinct palette role from the `bg` glass body.
GTK3 glass menus paint their background once on the outer popup; child menus
and decorations stay transparent. With local themed XFCE notifications, the
notifications layer manages `/initial-opacity` at 1 so the daemon does not
multiply the CSS alpha by its default 0.9.

The system-Python fixture `scripts/glass-popup.py gtk` (or `qt`) opens a
short-lived actual toolkit menu and reports its X11 ID and size. GTK fixtures
require PyGObject/GTK3; Qt fixtures require PyQt5. The fixture selects an RGBA
visual for GTK, writes no theme config, and takes focus while open; the parent
measurement must save/restore focus. `--css PATH` lets a GTK fixture load a
candidate stylesheet without installing it. See the
[menu/notification acceptance record](docs/acceptance/2026-10-05-menu-notification.md)
for the measured default and candidate results.

`shader_titlebar = "auto"` (the default) reads the configured XFWM theme's frame
images. A nonnegative integer overrides the top height; `0` switches frame
shading off. Side/bottom borders use their actual image dimensions. Maximized
windows with no side/bottom frame get a separate top-only rule.

`monocoat doctor` checks the reference shader's screen-space scanline raster and
premultiplied-alpha depth correction. Those source checks do not prove rendered
pixels. For an opt-in live X11 measurement, select an empty 32×8 region in each
visible application (a `frame` surface takes the FRAME window id with an offset
inside the titlebar), then run:

```bash
xdotool search --onlyvisible --class kitty
xdotool search --onlyvisible --class Rofi
monocoat verify --surface kitty:0xWINDOW_ID:200:100 \
                --surface rofi:0xWINDOW_ID:200:100 \
                --surface frame:0xFRAME_ID:200:8 --json
```

Replace the IDs and offsets with the actual windows/empty regions. Offsets are
relative to each client (for `frame`, inside the titlebar). The command puts a
128×64 black/white patch below each window, reads lit/dim screen rows, solves
alpha and premultiplied RGB, and fails on any RGB-byte or screen-phase
difference. It rejects text, gradients, opaque or obscured samples and
unsupported sessions. The temporary patch is destroyed even on failure, focus
is restored, and no theme files or application configs are written. Exit 0
proves equality only for the selected regions; JSON names catalogued surfaces
that were not measured (`kitty`, `rofi`, `dolphin`, `gtk-menu`, `qt-menu`,
`notification`, `task-list`, `frame`). Straight RGB is an estimate because
premultiplication rounds; exact equality uses the measured black/white pixels.

After glass/palette changes, apply lists running known applications that may
retain old settings until restarted. This notice is not a runtime acceptance
claim. `./scripts/check` runs pinned lint and the sandboxed test suite before
pushing; the suite includes the schema-document drift gate.

The resident dock also accepts `monocoat dock --toggle`, `--show`, `--hide` and
`--launchpad` (open or close its Launchpad) for hotkeys. Its preset controls four edges, per-output scale, visibility
policies, icon effects, built-in/file pins and opt-in lock-gated window
previews. See
[the dock guide](docs/THEME-DESIGNER-GUIDE.md#the-monocoat-dock-backend).
