A settings save takes effect at once, with no restart. (PERC-0075)
Rules, exclusions, snaps, layouts and profiles edited in the settings
window did nothing until Perch restarted. The next window event now
obeys them. Windows already placed stay where they are.
KDE: Perch now hears when you move or resize a window (PERC-0046)
The helper script Perch runs inside KWin used a timer call that doesn't
exist there, so every window move or resize notification failed
silently. Perch never learned where you had put your windows. Windows
whose app name arrives late also went unreported. Both now work, which
means Perch remembers your moves on KDE Plasma. The helper script's
JavaScript is now checked by a linter on every push.
The GNOME and Sway backends no longer claim abilities they lack (PERC-0044)
On GNOME and Sway, Perch cannot yet notice windows opening or monitors
changing, and on GNOME it cannot register shortcuts. It now says so
instead of claiming it can. Behaviour on those desktops is unchanged.
Release and check scripts no longer hide their own mistakes (PERC-0065)
Behind-the-scenes fixes to the tools that build, check and publish
Perch: a mismatched version is caught before a push rather than at
release, a manual rebuild of an old release refuses to attach the
wrong version, and the AUR and openSUSE submission scripts check what
they upload. No change to the app itself.
A layout no longer stacks two windows of the same app on one spot (PERC-0066)
When a layout has one spot for an app and two of that app's windows
are open, Perch now moves only the one you're using, or the first one
if you're using neither. The other stays where it is.
Starting Perch twice no longer runs two copies that fight over your windows (PERC-0049)
If Perch is already running, a second launch now says so and exits,
instead of starting a second copy that fights the first over your
windows and the saved-positions file. A copy that crashed does not
block the next start.
KDE: Perch reconnects after KWin restarts, and never acts on a stale command (PERC-0060)
If KWin crashes or restarts, Perch now notices, reloads its helper
script and reconnects, instead of silently doing nothing while the
tray still shows it as connected. A window move that timed out is
dropped rather than carried out late. Shortcuts keep working after a
brief D-Bus hiccup. The script installer will no longer replace a
folder that is not its own, even if PERCH_KWIN_SCRIPT_TARGET points
at one by mistake.
Autostart and startup: no false "failed" under Flatpak, clean exit on a startup error (PERC-0064)
Turning "Start at login" on or off under Flatpak no longer waits five
minutes and logs a failure for a change that worked. If startup fails
after Perch has connected to your desktop, it now disconnects cleanly
instead of leaving its KDE helper loaded. Logging out now always stops
Perch properly. A relative path in an XDG folder variable is ignored,
as the standard requires.
Flatpak on KDE: registering shortcuts through the desktop portal no longer times out (PERC-0048)
Perch now listens for the portal's answer before asking, so a quick
answer is no longer missed. If the portal still cannot set up
shortcuts, Perch falls back to KDE's own shortcut service instead of
failing to start its KDE support.
Settings window and tray: errors you can see and hear, your desktop theme respected (PERC-0058)
The Auto theme no longer replaces your desktop's colours (Breeze,
high-contrast) with Perch's own. Invalid match fields now get a red
outline and are announced by screen readers. A save that fails says why
and opens the page with the problem. Importing a non-text file shows an
error instead of doing nothing. "Apply preset" can no longer silently
fail to move the window. The Ctrl++ shortcut keeps its + key when
registered through the desktop portal. The Rules and Layouts tables now
describe the same placement the same way. A missing tray icon is logged.
Close the remaining X11 EWMH and ICCCM defects. (PERC-0047)
Hotkeys now fire with ScrollLock on, a held hotkey fires once instead of
repeating, and "Ctrl++" registers. A desktop without the RandR extension
starts instead of failing. Closing a window that offers no close protocol
reports that rather than killing the application. Restoring a minimised
window works, and a monitor the desktop reports no working area for keeps
its full size instead of collapsing to nothing.
Module docstrings that described shipped code as still to come (PERC-0062)
The X11 and KWin backend docstrings said their commands and hotkey
registration had not landed and would raise; both are v1 backends and
complete. Same class in the core modules, the app entry point, the rules
table model and the KWin hotkey path — all rewritten to describe what the
code does.
Instructions that named retired tools and a retired drive (PERC-0062)
audit_config.yaml and CLAUDE.md drove the drift analyser from a path
on the failed /mnt/Storage drive, with a flag the analyser does not
accept, so nobody following them could run it. The docs also mandated
four Claude Code skills that no longer exist, and two of them stated that
local_CI.sh runs one interpreter, which stopped being true when it
learned to read ci.yml's matrix.
A failing config migration reports the problem instead of a traceback (PERC-0059)
A missing migration step escaped as a bare KeyError. It is now a
ConfigError naming the file and the version gap, which is what docs/02
§Schema reference promises.
A typo'd top-level section is reported instead of ignored (PERC-0059)
Unknown keys inside [general] and [exclusions] were rejected, but an
unknown table at the top level was not — so [[rule]] for [[rules]]
meant every rule in the file went unread with no error. schema_version = true was also accepted, because a bool is an int in Python.
A hand edit to config.toml is no longer discarded silently (PERC-0059)
The settings dialog parses config.toml when it opens and writes it whole
on Apply, so anything edited on disk in between was lost. Perch offers
hand-editing as a supported workflow, so Apply now compares the file
against what it read and asks before replacing it.
Editing a profile in the dialog is validated the way adding one is (PERC-0059)
Renaming a profile to a name another already uses, or giving it a
topology that is a duplicate or malformed, went through none of the
checks add_profile applies. The dialog wrote it and the loader refused
the file on the next start, with nothing said at the time.
X11: a window is moved to its target desktop before it is placed (PERC-0068)
The two v1 backends disagreed — KWin sent the desktop change first, X11
sent it last. A window manager is free to re-place a window when its
desktop changes, so the placement has to be the last word. Both messages
still go out before a single flush.
Excluded and unidentifiable windows are no longer remembered (PERC-0057)
A window covered by an exclusion was written to state.json on any
user-initiated move, so Perch would restore later exactly what the
exclusion asked it to leave alone. A window reporting neither app_id
nor wm_class was stored under one shared key, so unrelated windows
overwrote each other's geometry.
The maximize fallback places the window on the resolved target monitor (PERC-0057)
Where a backend rejects native maximize, Perch substitutes the work-area
rectangle. It resolved that against the window's own monitor, which is
where the window sat before the same action moved it. The log line now
names the rule, as docs/02 specifies.
maximized = false unmaximizes before a monitor or desktop move (PERC-0057)
The pre-move unmaximize only ran when the action also carried a
geometry, so on a compositor that ignores writes to a maximized window
the move was dropped — the backend the rule exists for.
Percent geometry is clamped to the target monitor's work area (PERC-0057)
Percentages are not range-checked when the config is parsed, so a
negative or oversized one placed the window off-screen — against
docs/07's promise that a rule cannot.
monitor = "all" is rejected when the config loads (PERC-0057)
It validated and then failed on every window it matched. Nothing fans a
single action out across every output; the valid forms are an output
name, primary, current, or an integer index.
A match block combining catch_all with other fields is rejected (PERC-0057)
catch_all short-circuits, so the other fields were never consulted and
one stray checkbox matched every window. The settings dialog refuses the
combination too, rather than writing a config it would then not load.
A profile's default_layout is applied when the profile activates (PERC-0057)
The field parsed, seeded the documented sample and was editable in the
dialog, and nothing read it: docking activated the profile and applied
no layout. The config loader now also rejects a default_layout that
names no declared layout.
Release and CI gate scripts no longer swallow their own failures (PERC-0056)
The library harvest, the Flatpak manifest rewrite, the OBS upload and
the CI/local lockstep check each had a path where a failure was
discarded and the step reported success.
AppImage: a program launched from Perch no longer inherits the bundle's libraries (PERC-0056)
Opening a link or the config folder from the tray launched the host
browser or file manager against the AppImage's bundled AlmaLinux 8
libraries, which could crash it. The entry point now records the
host's own library path and Perch restores it for anything it spawns.
Shutdown now cancels in-flight actions, and a failed state flush no longer strands the backend
Neither step matched docs/01-architecture.md §Teardown order: tasks ran
on into teardown, and an OSError from the state.json flush skipped
backend.stop() entirely, leaving the KWin script loaded.
Background actions now log their failures to perch.log
An intent task's exception was never retrieved, so asyncio reported it on
the unconfigured root logger — it reached stderr and never the file a bug
report attaches.
Hyprland queries now refuse when the backend is not connected
The guard existed but had an empty body, so a stopped backend still
queried the compositor and repopulated the caches stop() had cleared.
Perch now reports it when the Hyprland event stream closes
A clean EOF left the backend marked healthy while it would never deliver
another event.
A failed KWin start no longer leaves a script running inside the compositor
stop() returned early unless the connection had fully succeeded, so any
earlier failure left the bus name held and the injected KWin script
loaded with nothing owning it.
A backend that fails to start no longer exits Perch with a traceback
start() is a documented raiser on three backends — the ordinary trigger
is a second copy failing to take the D-Bus name — and it ran unguarded
after the tray was already visible. It now degrades to the UI-only mode
docs/01-architecture.md §Startup has always described.
Restoring a window to a negative position on X11 no longer crashes
_NET_MOVERESIZE_WINDOW coordinates are signed, but the wire packing is
unsigned, so any window remembered off the left or top edge raised
OverflowError outside the backend error taxonomy.
Minimising a window on X11 no longer makes Perch forget it permanently
ICCCM has the window manager unmap a client to iconify it, and Perch read
that as a close — emitting the terminal window_closed for a window still
open, after which nothing could restore it.
A profile that cannot be deleted no longer corrupts the ones after it
The failure was suppressed while the index remap still assumed it had
happened, so every later field write landed on the wrong profile.
Adding a layout and then renaming it no longer fails the save
The rename bookkeeping registered the new name as its own original, so
the add step was skipped and a description was written to a table that
was never created.
A failed save no longer silently discards the other panes' edits
The document was rolled back but each pane that had already committed had
frozen its baseline, so it reported clean and was never committed again.
Panes now stay dirty until the write to disk has landed.
Apply then OK no longer deletes a rule or exclusion you did not touch
The Rules and Exclusions panes never refreshed their baseline after a
successful commit, so a second commit re-applied the first delete against
the original index — which by then addressed a surviving entry.
A symlinked config.toml is followed instead of being replaced
rename(2) acts on the link, so the first save moved the symlink itself
to .bak and dropped a regular file in its place — silently detaching a
config symlinked into a dotfiles repo.
Fractional percent geometries survive an unrelated layout edit
They were rounded to whole percents on every re-serialisation, so 12.5%
silently became 12% across the file.
Editing a layout no longer writes a config.toml Perch cannot read back
A window type was serialised as a TOML array; the reader accepts only a
comma-separated string and refuses an array outright. Because a layout
edit re-serialises every entry, one unrelated change was enough.
A config.toml with a non-UTF-8 byte now falls back to the backup
It raised UnicodeDecodeError, which was outside the caught set, so it
skipped the fallback and surfaced as a traceback.
A config.toml from a newer Perch is refused rather than silently replaced by the backup
It was treated as corruption, so an older config loaded and the newer one
was rotated away on the next save. It now exits non-zero with a pinpoint
message, per docs/02-state-format.md §Schema reference.
A missing config.toml beside an intact backup now recovers instead of reseeding
The atomic write's own crash window leaves exactly that state. Perch
seeded defaults over it, and the next save rotated the surviving backup
away — losing every rule, layout and profile.
A window geometry saved during a state.json write is no longer dropped
The dirty flag was cleared after the write rather than with the snapshot,
so a change landing mid-write was marked clean without being saved.
A malformed record in state.json no longer stops Perch starting
A missing field raised KeyError past the handler, so the documented
.bak fallback never ran and the exception escaped startup.
A state.json from a newer Perch is no longer destroyed by an older one
Rolling back a version discarded the newer file, then rotated it into
.bak and overwrote both within seconds of the first window event —
losing every remembered geometry. The store now latches read-only, as
docs/02-state-format.md §Versioning and migration always specified.