Problem Statement
The desktop runs three physical monitors through the Monitor-Anchored Workspace Model in the icyleaf.workspaces bar widget. Two spatial gaps remain in daily work:
- No cross-screen occupancy overview. The bar on any one monitor only shows that monitor's own 10-slot workspace set. From the M1 bar you cannot see which of M2/M3's workspaces are busy or which slot is active on those screens.
- No per-monitor spatial preview. The existing interactive overview (Mirador) flattens workspaces into a one-dimensional grid and does not express where each physical display sits in the global layout, nor how windows are arranged on a given screen at a glance.
The user wants a read-only "current multi-display layout" preview summoned from the bar: right-clicking a monitor's Monitor Identity Badge reveals where every display physically sits, what its active workspace looks like, and which of its workspaces are occupied.
Solution
Extend the icyleaf.workspaces bar widget with a read-only Per-Monitor Layout Preview Overlay. Right-clicking any Monitor Identity Badge opens a full-screen overlay on that badge's own screen, pre-selecting that monitor. The overlay renders every enabled physical monitor as a spatial card reproduced at true global x/y geometry (scale-compensated), labeled with its badge (M<n>), real monitor id, connector, and description. Each card shows the monitor's 10-slot occupancy strip plus its active workspace's windows drawn as rectangles at true relative geometry (groups as one frame with a member count, fullscreen fills the card, tiny windows clamped to a readable minimum). Clicking a card focuses that monitor and dismisses; Esc or clicking empty space dismisses; arrow keys move selection between monitor cards and Return focuses the selected monitor. The overlay refreshes live from Hyprland events while open, shows no screenshots, performs no window/workspace mutation, adds no permanent bar chrome, and coexists with (rather than replacing) the interactive Mirador overview.
User Stories
- As a multi-monitor desktop user, I want to right-click a monitor's badge (
M1/M2/M3) to open a layout preview, so that I can inspect the whole display setup from wherever I am.
- As a multi-monitor desktop user, I want the preview to open full-screen on the monitor whose badge I clicked, with that monitor selected, so that the preview appears where I am looking.
- As a multi-monitor desktop user, I want every enabled physical monitor rendered as a card, so that the preview reflects all displays in one view.
- As a multi-monitor desktop user, I want cards positioned and sized by each monitor's true global coordinates and aspect ratio, so that the preview reproduces the physical arrangement (including a display mounted above-left of another and negative coordinates).
- As a multi-monitor desktop user, I want each card header to show its badge (
M<n>), real Hyprland monitor id, connector name, and description, so that I can tell which physical display is which.
- As a multi-monitor desktop user, I want the card whose display currently holds global input focus to be visually highlighted, so that I can see where my keyboard focus sits in the layout.
- As a multi-monitor desktop user, I want each card to carry a fixed 10-slot occupancy strip for that monitor's workspace range, so that I can see at a glance which workspaces on every other screen are occupied, active, or empty.
- As a multi-monitor desktop user, I want the active slot and the globally focused workspace distinctively marked on the occupancy strip, so that activity state is unambiguous across screens.
- As a multi-monitor desktop user, I want each card's body to draw the active workspace's windows as rectangles at true relative geometry, so that I can see how windows are actually tiled on that screen.
- As a multi-monitor desktop user, I want tiled and floating windows both represented, so that the preview is faithful to real layouts.
- As a multi-monitor desktop user, I want a fullscreen window on a workspace to fill the whole card, so that fullscreen state is immediately obvious.
- As a multi-monitor desktop user, I want a Hyprland group drawn as a single frame showing its member count, so that grouped windows read as one unit without clutter.
- As a multi-monitor desktop user, I want windows too small to read to be expanded to a minimum visible size around their center, so that every window remains perceptible on small cards.
- As a multi-monitor desktop user, I want hovering a window rectangle to show its title, so that I can identify windows without any interaction side effects.
- As a multi-monitor desktop user, I want hovering a card header to reveal its full description when elided, so that long display names remain readable.
- As a multi-monitor desktop user, I want an idle monitor (empty active workspace) to still render its card with header, occupancy strip, and a faint empty hint, so that present-but-idle displays are not mistaken for absent ones.
- As a multi-monitor desktop user, I want to click a monitor card to focus that display and dismiss the overlay, so that "see it, then jump to it" is one gesture.
- As a multi-monitor desktop user, I want
Esc or clicking empty overlay space to dismiss, so that the overlay never traps me.
- As a multi-monitor desktop user, I want arrow keys to move selection between monitor cards and
Return to focus the selected monitor and dismiss, so that the overlay is fully usable without a mouse.
- As a multi-monitor desktop user, I want the overlay to be read-only, so that I never accidentally move a window or switch a workspace while just looking.
- As a multi-monitor desktop user, I want the layout to refresh live from Hyprland while the overlay is open, so that changes in focus, workspaces, or windows appear without reopening.
- As a multi-monitor desktop user, I want right-clicking anywhere on an open overlay to dismiss it, so that re-right-clicking where the badge sits toggles the preview closed (the badge is underneath the overlay).
- As a multi-monitor desktop user, I want right-clicking another monitor's badge while a preview is open to close the current preview and open one on that monitor's screen with that monitor selected, so that at most one preview exists and it follows my intent.
- As a multi-monitor desktop user, I want disabled or mirrored displays excluded from the preview, so that only real output surfaces are shown.
- As a multi-monitor desktop user, I want the bar itself unchanged when the overlay is closed, so that no permanent chrome is added for a preview I summon on demand.
- As a multi-monitor desktop user, I want the overlay to coexist with the Mirador interactive overview, so that both "see the layout" and "manage windows" remain available without overlapping.
- As a developer, I want a no-argument
preview IPC method on the widget target, so that a future keybinding can summon the preview for the globally focused monitor without rework.
- As a developer, I want the layout math extracted behind a pure-function seam, so that the geometry logic (the core of the feature) is testable with fixtures rather than only by eye.
Implementation Decisions
- Module modified: the
icyleaf.workspaces bar widget (a bar-widget plugin). No new plugin, no cross-plugin IPC, no summon plumbing.
- Trigger: the Monitor Identity Badge's press handler gains a button-aware branch — left click keeps the existing focus-monitor action; right click opens the preview. Each monitor has its own widget instance (one bar surface per screen), so the instance already knows its hosting screen, monitor id/name, and workspace offset.
- Overlay host: a full-screen
PanelWindow following the widget's existing quick-picker pattern: ExclusionMode.Ignore, WlrLayershell Overlay layer, exclusive keyboard focus while open, a distinct layershell namespace, and its screen anchored to the hosting instance's bar screen. The body is a dismiss-on-click background with a centered/arranged card scene; Mirador's bar-inset handling is the reference for keeping cards clear of the bar (reference only, not copied).
- Single-overlay invariant and re-anchor: each instance owns its own preview window, but only one may be open at a time. Opening on an instance broadcasts a "close others" via the widget base's existing per-module broadcast mechanism, then opens and selects its own monitor. Because the overlay covers only its host screen, a different monitor's badge is on an uncovered bar surface and can be right-clicked directly to re-anchor. Re-right-clicking where the current overlay sits lands on the overlay, which treats a right-click on the overlay surface as dismiss — yielding toggle semantics (Q19).
- Geometry seam: a new pure-JS
.pragma library layout-geometry module inside the widget plugin takes plain-data snapshots (monitors with global x/y/size/scale, per-monitor active workspace and its toplevel geometries, per-monitor 10-slot occupancy) plus a canvas size, and returns the full card layout model: card positions/sizes preserving true spatial relations and aspect ratios (scale-compensated via logical dimensions, negative global coordinates supported), per-card window rectangles (tiled, floating, fullscreen-fills, groups as one frame with member count, minimum-size clamp), occupancy slot states (occupied / active / globally focused / empty), and empty-workspace hint state. QML layers do only declarative assembly and event wiring over this seam.
- Card data: built from
Hyprland.monitors (enabled, non-mirrored only), each monitor's active workspace toplevel lastIpcObject geometry, and Hyprland.workspaces to resolve slot occupancy in that monitor's range. Global input focus is read from the focused monitor/workspace to drive card and strip highlighting.
- Header labeling: badge text (
M<n>), real numeric monitor id, connector name, and description (elided with full text on hover), per the Monitor Identity Badge conventions.
- Refresh: event-driven while open — subscribe to Hyprland
rawEvent and re-snapshot/recompute on monitor*, workspace*, window*, group*, fullscreen, changefloatingmode, and focusedmon events; teardown on close. Opening performs one initial snapshot. This mirrors the reactive pattern already used by the widget and by Mirador (behavioral reference only).
- Interactions: card click = focus that monitor via the existing Direct Hyprland IPC Dispatch seam and dismiss;
Esc and empty-space click dismiss; right-click on the overlay dismisses; arrow keys move card selection, Return focuses the selected monitor and dismisses.
- Focus transfer: focusing a monitor uses the widget's existing Lua dispatch form (
hl.dsp.focus on the monitor name), consistent with the plugin's current actions.
- IPC contract: add a no-argument
preview method to the widget's existing IPC target that summons the overlay for the globally focused monitor. No shell.json setting and no Hyprland binding are added now; the IPC seam is reserved for future keybinding.
- Domain records: covered by ADR 0004 (Proposed) and the CONTEXT.md entry Per-Monitor Layout Preview Overlay; the spec aligns with ADR 0004's decisions. Mirador's MIT-licensed geometry utilities are referenced for approach, never copied.
Testing Decisions
- What makes a good test: only externally observable layout output. Feed the geometry seam a fixture snapshot (real monitor layout including negative/overlapping global coordinates and mixed scale factors, occupied/empty/active/globally-focused slot states, tiled + floating + fullscreen windows, a grouped window set, an idle monitor) and assert the returned card model — spatial ordering and relative placement preserved, aspect ratios correct, coordinates within canvas, window rects ordered and clamped to a readable minimum, groups collapsed to one frame with the correct member count, fullscreen fills its card, slot states classified correctly, empty-workspace hint emitted. No assertions on QML internals or widget structure.
- Module tested: the pure-JS layout-geometry seam; it is the feature's core logic and the only seam needed. QML shells over it stay thin and are verified empirically.
- Prior art: this repo ships no committed QML test scaffolding for its
icyleaf plugins; the closest analogues are (a) the fixture/contract-test philosophy used by earlier agent specs on this tracker (e.g. the herdr Snapshot-to-ViewModel fixture contract tests), and (b) the third-party Mirador plugin's QtTest TestCase files and its extraction of geometry math into a pure-JS module — the conceptual model the seam mirrors. The live-desktop empirical path used by prior specs (chezmoi apply --force then omarchy-restart-shell) validates the overlay on the real three-monitor, mixed-scale setup.
Out of Scope
- Real-time screencopy thumbnails or any window screenshots inside cards.
- Interactive window management — dragging windows, activating windows, switching workspaces from the preview (Mirador remains the tool for these).
- Rendering disabled or mirrored displays, or special-workspace corner badges.
- Making the trigger configurable (shell.json gesture settings) or adding a Hyprland keybinding now.
- Any permanent bar chrome: the bar gains nothing when the preview is closed.
- Merging this into Mirador, refactoring Mirador, or changing how the two coexist.
Further Notes
- The current live layout doubles as the primary fixture: eDP-1 (id 0,
0 0, 2880x1800, scale 1.8), DP-1 (id 1, -10 -1080, 3240x2160, scale 2), DP-2 (id 2, 1610 0, 3840x2160, scale 2) — DP-1 sits up-left of eDP-1 and DP-2 to its right, exercising negative coordinates and mixed scales.
- The overlay sits above the bar (Overlay layer over Top layer); this is why same-screen re-trigger is expressed as "right-click on the overlay dismisses" rather than reaching the covered badge, and cross-monitor re-anchor works because other monitors' bars are never covered.
- Reserved
preview IPC keeps a future keybinding cheap; see ADR 0004 for the full context and consequences.
Problem Statement
The desktop runs three physical monitors through the Monitor-Anchored Workspace Model in the
icyleaf.workspacesbar widget. Two spatial gaps remain in daily work:The user wants a read-only "current multi-display layout" preview summoned from the bar: right-clicking a monitor's Monitor Identity Badge reveals where every display physically sits, what its active workspace looks like, and which of its workspaces are occupied.
Solution
Extend the
icyleaf.workspacesbar widget with a read-only Per-Monitor Layout Preview Overlay. Right-clicking any Monitor Identity Badge opens a full-screen overlay on that badge's own screen, pre-selecting that monitor. The overlay renders every enabled physical monitor as a spatial card reproduced at true global x/y geometry (scale-compensated), labeled with its badge (M<n>), real monitor id, connector, and description. Each card shows the monitor's 10-slot occupancy strip plus its active workspace's windows drawn as rectangles at true relative geometry (groups as one frame with a member count, fullscreen fills the card, tiny windows clamped to a readable minimum). Clicking a card focuses that monitor and dismisses;Escor clicking empty space dismisses; arrow keys move selection between monitor cards andReturnfocuses the selected monitor. The overlay refreshes live from Hyprland events while open, shows no screenshots, performs no window/workspace mutation, adds no permanent bar chrome, and coexists with (rather than replacing) the interactive Mirador overview.User Stories
M1/M2/M3) to open a layout preview, so that I can inspect the whole display setup from wherever I am.M<n>), real Hyprland monitor id, connector name, and description, so that I can tell which physical display is which.Escor clicking empty overlay space to dismiss, so that the overlay never traps me.Returnto focus the selected monitor and dismiss, so that the overlay is fully usable without a mouse.previewIPC method on the widget target, so that a future keybinding can summon the preview for the globally focused monitor without rework.Implementation Decisions
icyleaf.workspacesbar widget (a bar-widget plugin). No new plugin, no cross-plugin IPC, no summon plumbing.PanelWindowfollowing the widget's existing quick-picker pattern:ExclusionMode.Ignore, WlrLayershellOverlaylayer, exclusive keyboard focus while open, a distinct layershell namespace, and its screen anchored to the hosting instance's bar screen. The body is a dismiss-on-click background with a centered/arranged card scene; Mirador's bar-inset handling is the reference for keeping cards clear of the bar (reference only, not copied)..pragma librarylayout-geometry module inside the widget plugin takes plain-data snapshots (monitors with global x/y/size/scale, per-monitor active workspace and its toplevel geometries, per-monitor 10-slot occupancy) plus a canvas size, and returns the full card layout model: card positions/sizes preserving true spatial relations and aspect ratios (scale-compensated via logical dimensions, negative global coordinates supported), per-card window rectangles (tiled, floating, fullscreen-fills, groups as one frame with member count, minimum-size clamp), occupancy slot states (occupied / active / globally focused / empty), and empty-workspace hint state. QML layers do only declarative assembly and event wiring over this seam.Hyprland.monitors(enabled, non-mirrored only), each monitor's active workspace toplevellastIpcObjectgeometry, andHyprland.workspacesto resolve slot occupancy in that monitor's range. Global input focus is read from the focused monitor/workspace to drive card and strip highlighting. M<n>), real numeric monitor id, connector name, and description (elided with full text on hover), per the Monitor Identity Badge conventions.rawEventand re-snapshot/recompute onmonitor*,workspace*,window*,group*,fullscreen,changefloatingmode, andfocusedmonevents; teardown on close. Opening performs one initial snapshot. This mirrors the reactive pattern already used by the widget and by Mirador (behavioral reference only).Escand empty-space click dismiss; right-click on the overlay dismisses; arrow keys move card selection,Returnfocuses the selected monitor and dismisses.hl.dsp.focuson the monitor name), consistent with the plugin's current actions.previewmethod to the widget's existing IPC target that summons the overlay for the globally focused monitor. No shell.json setting and no Hyprland binding are added now; the IPC seam is reserved for future keybinding.Testing Decisions
icyleafplugins; the closest analogues are (a) the fixture/contract-test philosophy used by earlier agent specs on this tracker (e.g. the herdr Snapshot-to-ViewModel fixture contract tests), and (b) the third-party Mirador plugin's QtTestTestCasefiles and its extraction of geometry math into a pure-JS module — the conceptual model the seam mirrors. The live-desktop empirical path used by prior specs (chezmoi apply --forcethenomarchy-restart-shell) validates the overlay on the real three-monitor, mixed-scale setup.Out of Scope
Further Notes
0 0, 2880x1800, scale 1.8), DP-1 (id 1,-10 -1080, 3240x2160, scale 2), DP-2 (id 2,1610 0, 3840x2160, scale 2) — DP-1 sits up-left of eDP-1 and DP-2 to its right, exercising negative coordinates and mixed scales.previewIPC keeps a future keybinding cheap; see ADR 0004 for the full context and consequences.