niri: working minimize via declared stash workspace #1

Merged
funman300 merged 5 commits from feat/niri-minimize into main 2026-07-16 17:19:57 +00:00
Owner

Mod+M and Mod+Shift+M already existed in niri/config.kdl, referencing a
"minimized" workspace that was never declared. niri silently ignores
actions targeting an undeclared named workspace — and niri validate passes
anyway, because the name is just a string to the parser. Both binds had always
been no-ops.

This declares the stash and builds the restore half that was never there.

What you get

Key Action
Mod+M Stash the focused window. Focus stays where it is.
Mod+Shift+M wofi picker of stashed windows → restore the pick to the current workspace, focused.

Verified niri constraints

Tested against niri 26.04 in a nested instance rather than inferred from docs:

  1. An undeclared named workspace is a silent no-op; niri validate still passes.
  2. A declared named workspace is pinned to index 1 and cannot be moved
    (move-workspace-to-index --reference refuses; open-on-output to a fake
    output doesn't dislodge it). This is why Mod+1..9 are offset by +1
    the offset is exact and stable, not a guess.
  3. move-window-to-workspace defaults to focus=true, which drags focus into
    the stash — the opposite of minimizing. Hence focus=false on Mod+M.

Notes

  • Restores to the current workspace ("bring it to me"), so no origin tracking
    exists to go stale.
  • The minimized pill in waybar is permanent by design — it's the only cue that
    windows are stashed.
  • Final review caught a TOCTOU: the picker blocks while niri can renumber
    workspaces underneath it, so main() re-reads state after the pick rather
    than trusting anything captured before wofi opened (05cc026).

Testing

  • python3 scripts/tests/window-restore.test.py — 21/21, pure logic, no compositor.
  • niri validate -c niri/config.kdl passes.
  • Six keyboard checks (focus stays on Mod+M, picker round-trip, empty-stash
    toast, offset binds) verified by hand — no wtype/ydotool on this box.

Spec: docs/superpowers/specs/2026-07-16-niri-minimize-design.md
Plan: docs/superpowers/plans/2026-07-16-niri-minimize.md

`Mod+M` and `Mod+Shift+M` already existed in `niri/config.kdl`, referencing a `"minimized"` workspace that was **never declared**. niri silently ignores actions targeting an undeclared named workspace — and `niri validate` passes anyway, because the name is just a string to the parser. Both binds had always been no-ops. This declares the stash and builds the restore half that was never there. ## What you get | Key | Action | |---|---| | `Mod+M` | Stash the focused window. Focus stays where it is. | | `Mod+Shift+M` | wofi picker of stashed windows → restore the pick to the **current** workspace, focused. | ## Verified niri constraints Tested against niri 26.04 in a nested instance rather than inferred from docs: 1. An undeclared named workspace is a silent no-op; `niri validate` still passes. 2. A declared named workspace is **pinned to index 1 and cannot be moved** (`move-workspace-to-index --reference` refuses; `open-on-output` to a fake output doesn't dislodge it). **This is why `Mod+1..9` are offset by +1** — the offset is exact and stable, not a guess. 3. `move-window-to-workspace` defaults to `focus=true`, which drags focus into the stash — the opposite of minimizing. Hence `focus=false` on `Mod+M`. ## Notes - Restores to the *current* workspace ("bring it to me"), so no origin tracking exists to go stale. - The `minimized` pill in waybar is permanent by design — it's the only cue that windows are stashed. - Final review caught a TOCTOU: the picker blocks while niri can renumber workspaces underneath it, so `main()` re-reads state after the pick rather than trusting anything captured before wofi opened (05cc026). ## Testing - `python3 scripts/tests/window-restore.test.py` — 21/21, pure logic, no compositor. - `niri validate -c niri/config.kdl` passes. - Six keyboard checks (focus stays on `Mod+M`, picker round-trip, empty-stash toast, offset binds) verified by hand — no `wtype`/`ydotool` on this box. Spec: `docs/superpowers/specs/2026-07-16-niri-minimize-design.md` Plan: `docs/superpowers/plans/2026-07-16-niri-minimize.md`
funman300 added 5 commits 2026-07-16 17:19:26 +00:00
Mod+M and Mod+Shift+M reference a "minimized" named workspace that was never
declared, so both binds have always been silent no-ops. Design covers the
declaration plus a wofi restore picker.

Constraints verified against niri 26.04 in a nested instance: undeclared named
workspaces are no-ops, declared ones are pinned to index 1 and cannot be moved
(hence the +1 offset on Mod+1..9), and focus=false is required or focus follows
the window into the stash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two tasks: window-restore.py + unit tests, then the niri config declaration,
bind rewiring and install.sh symlink. Script precedes config so Mod+Shift+M is
never wired to a missing script.

Plan code verified by extracting both blocks and running the test suite against
the script: 18/18 pass, and the step-2 failure mode is confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure logic split from the niri/wofi calls so it unit-tests without a
compositor. Restores to the current workspace, so no origin tracking.
Mod+M referenced an undeclared 'minimized' workspace, so it silently did
nothing. Declare it, add focus=false so focus does not follow the window
into the stash, and point Mod+Shift+M at the restore picker.

niri pins declared named workspaces to index 1 and will not move them, so
Mod+1..9 are offset by one.
The picker blocks on user input for however long they take. In that
window niri can destroy a dynamic workspace (its last window closed)
and renumber every later one down, so the pre-pick current["idx"] can
point at the wrong workspace by the time move-window-to-workspace
runs — restoring to the wrong place. The stashed window itself can
also have closed in the meantime, in which case the move/focus calls
would silently no-op. main() now re-reads workspaces/windows after
pick() returns, recomputes the current workspace, and confirms the
chosen window still exists before acting; each bails with a notify
and the documented exit code otherwise. Adds window_exists() as a
pure, tested helper alongside the existing pure functions.

Also: clarify the focus=false comment in niri/config.kdl (it read as
if focus=false caused the problem it actually prevents), and note in
the design doc's Out of scope section that the stash's index-1
pinning is per-output, so a second monitor would need its own
analysis of the Mod+1..9 offset.
funman300 merged commit b8a858d7a7 into main 2026-07-16 17:19:57 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/dotfiles#1