Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-964GitHub issuePublicDecided· Out of scope

Workspace highlight stays on the destination monitor's old workspace after moving a workspace between monitors (focusedmon precedes moveworkspacev2)

Reported by sedulam · filed Sep 4, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaOutside the audited area· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Workspace highlight stays on the destination monitor's old workspace after moving a workspace between monitors (focusedmon precedes moveworkspacev2)revision 1 · captured 2026-09-28 21:24 UTC via poll

System details

Intel Core i7-8665U, Intel UHD Graphics 620 (WhiskeyLake-U GT2), Omarchy 4.0.2-1, Hyprland 0.56.2, quickshell 0.3.1-1. Two monitors: HDMI-A-2 (left) and eDP-1 (right).

What's wrong?

Sending a workspace to another monitor (SUPER+SHIFT+ALT+LEFT/RIGHT, i.e. hl.dsp.workspace.move({ monitor = "r" })) leaves the bar on the destination monitor highlighting the workspace that monitor showed before the move. It never self-corrects — it stays wrong until some later unrelated workspace event.

This looks like #8183 / #8364 but is a different root cause, and I verified it is not fixed by #8997 (details below).

Steps to reproduce

  1. Two monitors, a different workspace active on each — here HDMI-A-2 focused on workspace 8, eDP-1 on workspace 7.
  2. Press SUPER+SHIFT+ALT+RIGHT to send workspace 8 to eDP-1.
  3. Hyprland moves workspace 8 onto eDP-1 and focuses eDP-1. hyprctl monitors confirms: eDP-1 focused=true activeWorkspace=8.
  4. The bar on eDP-1 highlights 7.

Root cause: event ordering

Reading Hyprland's event socket across the move:

workspacev2>>2,2                 # source monitor falls back to another workspace
focusedmon>>eDP-1,7              # focus moves — but names the workspace eDP-1
                                 # held BEFORE the move
moveworkspacev2>>8,8,eDP-1       # the workspace only lands now

focusedmon arrives before moveworkspacev2 and reports the destination monitor's pre-move workspace. Quickshell latches that value and gets no correction afterwards, so its cache goes stale — both Hyprland.focusedWorkspace and the per-monitor HyprlandMonitor.activeWorkspace.

Instrumented inside the widget (logging on every raw event), for a move where the true end state was HDMI-A-2 focused on workspace 5 and eDP-1 on 6:

ev:moveworkspacev2   focusedWorkspace=8  focusedMonitor=HDMI-A-2  fm.activeWorkspace=8  monitors=[HDMI-A-2=8 eDP-1=6]
after-refresh        focusedWorkspace=5  focusedMonitor=HDMI-A-2  fm.activeWorkspace=5  monitors=[HDMI-A-2=5 eDP-1=6]

The first line is what Quickshell believes once every event has been delivered; the second is after a single Hyprland.refreshMonitors(). Note monitors=[HDMI-A-2=8 ...] in the stale line — the per-monitor activeWorkspace is wrong too, not just the global one.

Why #8997 does not cover this

I applied #8997 on top of 4.0.2-1 as a cloned widget and repeated the move. Result:

  • Source monitor HDMI-A-2: highlights 2, and Hyprland reports 2. Correct — #8997 works as intended here.
  • Destination monitor eDP-1: highlights 7, while Hyprland reports 8. Still wrong.

That is expected from the trace above: #8997 switches the binding to Hyprland.monitorFor(window.screen).activeWorkspace, which reads the same stale cache that focusedWorkspace does. #8997 fixes which monitor's workspace is displayed; it does not make that value fresh after a workspace move.

Suggested fix

Ask Quickshell to re-read hyprctl monitors once the move has landed. This restores focusedMonitor.activeWorkspace and focusedWorkspace together, and composes with #8997 rather than conflicting with it:

  // Hyprland emits `focusedmon` with the destination monitor's pre-move
  // workspace before `moveworkspacev2` lands the workspace, and sends no
  // correction afterwards, so Quickshell's cached monitor state goes stale.
  // The timer collapses the moveworkspace/moveworkspacev2 pair into one refresh.
  Timer {
    id: monitorRefresh
    interval: 0
    onTriggered: Hyprland.refreshMonitors()
  }

  Connections {
    target: Hyprland
    function onRawEvent(event) {
      if (event.name === "moveworkspace" || event.name === "moveworkspacev2") monitorRefresh.restart()
    }
  }

Verification

Ran this as a cloned widget and moved a workspace in both directions repeatedly: the highlight tracks the workspace that actually has focus every time, on both bars. Screenshots of the bar before and after (captured with grim against the real second output) confirm it, and hyprctl monitors was cross-checked against the bar on each run.

Note that iterating on this is easy to get wrong: saving a bar widget's .qml logs Local plugin changed, reloading: <id> but does not re-instantiate the widget, so new Connections/Component.onCompleted code silently never runs. omarchy restart shell is needed to actually test a change.

Happy to open a PR with this if it's wanted — it's a small addition and it stacks cleanly on #8997.

View raw text
### System details

Intel Core i7-8665U, Intel UHD Graphics 620 (WhiskeyLake-U GT2), Omarchy 4.0.2-1, Hyprland 0.56.2, quickshell 0.3.1-1. Two monitors: `HDMI-A-2` (left) and `eDP-1` (right).

### What's wrong?

Sending a workspace to another monitor (`SUPER+SHIFT+ALT+LEFT/RIGHT`, i.e. `hl.dsp.workspace.move({ monitor = "r" })`) leaves the bar on the destination monitor highlighting the workspace that monitor showed *before* the move. It never self-corrects — it stays wrong until some later unrelated workspace event.

This looks like #8183 / #8364 but is a **different root cause**, and I verified it is not fixed by #8997 (details below).

### Steps to reproduce

1. Two monitors, a different workspace active on each — here `HDMI-A-2` focused on workspace 8, `eDP-1` on workspace 7.
2. Press `SUPER+SHIFT+ALT+RIGHT` to send workspace 8 to `eDP-1`.
3. Hyprland moves workspace 8 onto `eDP-1` and focuses `eDP-1`. `hyprctl monitors` confirms: `eDP-1 focused=true activeWorkspace=8`.
4. The bar on `eDP-1` highlights **7**.

### Root cause: event ordering

Reading Hyprland's event socket across the move:

```
workspacev2>>2,2                 # source monitor falls back to another workspace
focusedmon>>eDP-1,7              # focus moves — but names the workspace eDP-1
                                 # held BEFORE the move
moveworkspacev2>>8,8,eDP-1       # the workspace only lands now
```

`focusedmon` arrives *before* `moveworkspacev2` and reports the destination monitor's pre-move workspace. Quickshell latches that value and gets no correction afterwards, so its cache goes stale — both `Hyprland.focusedWorkspace` **and** the per-monitor `HyprlandMonitor.activeWorkspace`.

Instrumented inside the widget (logging on every raw event), for a move where the true end state was `HDMI-A-2` focused on workspace 5 and `eDP-1` on 6:

```
ev:moveworkspacev2   focusedWorkspace=8  focusedMonitor=HDMI-A-2  fm.activeWorkspace=8  monitors=[HDMI-A-2=8 eDP-1=6]
after-refresh        focusedWorkspace=5  focusedMonitor=HDMI-A-2  fm.activeWorkspace=5  monitors=[HDMI-A-2=5 eDP-1=6]
```

The first line is what Quickshell believes once every event has been delivered; the second is after a single `Hyprland.refreshMonitors()`. Note `monitors=[HDMI-A-2=8 ...]` in the stale line — the per-monitor `activeWorkspace` is wrong too, not just the global one.

### Why #8997 does not cover this

I applied #8997 on top of 4.0.2-1 as a cloned widget and repeated the move. Result:

- Source monitor `HDMI-A-2`: highlights 2, and Hyprland reports 2. Correct — #8997 works as intended here.
- Destination monitor `eDP-1`: highlights **7**, while Hyprland reports 8. Still wrong.

That is expected from the trace above: #8997 switches the binding to `Hyprland.monitorFor(window.screen).activeWorkspace`, which reads the same stale cache that `focusedWorkspace` does. #8997 fixes *which* monitor's workspace is displayed; it does not make that value fresh after a workspace move.

### Suggested fix

Ask Quickshell to re-read `hyprctl monitors` once the move has landed. This restores `focusedMonitor.activeWorkspace` and `focusedWorkspace` together, and composes with #8997 rather than conflicting with it:

```qml
  // Hyprland emits `focusedmon` with the destination monitor's pre-move
  // workspace before `moveworkspacev2` lands the workspace, and sends no
  // correction afterwards, so Quickshell's cached monitor state goes stale.
  // The timer collapses the moveworkspace/moveworkspacev2 pair into one refresh.
  Timer {
    id: monitorRefresh
    interval: 0
    onTriggered: Hyprland.refreshMonitors()
  }

  Connections {
    target: Hyprland
    function onRawEvent(event) {
      if (event.name === "moveworkspace" || event.name === "moveworkspacev2") monitorRefresh.restart()
    }
  }
```

### Verification

Ran this as a cloned widget and moved a workspace in both directions repeatedly: the highlight tracks the workspace that actually has focus every time, on both bars. Screenshots of the bar before and after (captured with `grim` against the real second output) confirm it, and `hyprctl monitors` was cross-checked against the bar on each run.

Note that iterating on this is easy to get wrong: saving a bar widget's `.qml` logs `Local plugin changed, reloading: <id>` but does not re-instantiate the widget, so new `Connections`/`Component.onCompleted` code silently never runs. `omarchy restart shell` is needed to actually test a change.

Happy to open a PR with this if it's wanted — it's a small addition and it stacks cleanly on #8997.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

Not linked to any known issue, other report or change yet.

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Out of scopeDecided by agents · verified · 2026-10-09 10:48 UTC