The workspace/window panel couldn't be scrolled: it snapped back to the top
about once a second. Nothing to do with the hologram overlay (a DrawingArea
with can_target=False that only ever queue_draw()s itself) — it was the 1 Hz
refresh that start_polling() runs while the menu is open. Both of its
callbacks called _rebuild_panel(), which did _clear(self._panel) and rebuilt
every widget, including a fresh Gtk.ScrolledWindow for the list. Emptying
scrolled content collapses the scroll adjustment's upper bound, so its value
is clamped back to 0 and the viewport jumps to the top.
The panel scaffolding (header, layout stack + switcher, list scroller) is now
built once in _build_panel_scaffold() and refreshed in place by _update_panel:
* The layout tabs are the same for every workspace — only the selection is
per-workspace, and _sync_layout_controls() already refreshes that — so
they no longer get torn down either.
* _update_window_list() compares the sorted window addresses with the last
set. Unchanged: row contents (title, ws N, icon) are updated in place, so
a retitled window can't move the viewport. Changed: only the rows are
rebuilt, and both scrollers' offsets are saved and restored on idle, once
the new rows are allocated and the adjustment's upper is no longer stale.
* _rebuild_strip() gets the same signature guard, so the poll stops
resetting the compact strip's horizontal scroll and no longer tears down a
grouped app's instance popover while it's open. It keys on addresses only:
strip titles are tooltip/popover-only and refresh on the next real change.
Applied identically to hyprlua and hyprdrive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>