fix: the sidebar button follows the panel, and the wish survives having no source

The toolbar button was the panel's only input: `update_side_visibility()` derived the
panel from the button and never the other way, so the button went on saying "shown" in
two cases where the panel was off -- while there were no signals to show, and after
anything else moved the split view's state.

Binding the two together is not the fix. AdwOverlaySplitView writes `show-sidebar`
itself, at startup and when it collapses, with no user involved; a binding pushes that
into the button, the button's handler reads it as the user's wish, and the sidebar then
never comes back when a source connects -- a worse bug than the one reported, and one
that a "was this our own write" flag does not catch either, because the toggle's signal
may fire after the write returns.

So the direction is stated instead of inferred: `show-sidebar` is an output, the button
is the only input, and `App::sidebar_wanted` holds the wish between them. The wish is
written by the toggle's handler alone (guarded while we set the button ourselves), the
no-signals case forces the panel off without touching the wish, and tick() re-asserts
both from the wish when the widget disagrees -- so a hide gesture does not stick either.

[sidebar-probe] now reads the pair (shown vs button) in both states and against a hide
that did not come from the button; shown=1 button=1 with a source, shown=0 button=0
without, and the source reconnecting brings both back is the reading this exists for.
This commit is contained in:
Martino Ferrari committed 2026-10-12 13:23:40 +02:00
1 parent bb01278d88
commit 2d9f327da7
5 files changed
+95 -16

No files matched your search

+11
View File
@@ -16,6 +16,17 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
from the window's width in `tick()`) to *overlay* the plot when there is no room beside it rather than
being squeezed. `[sidebar-probe]` reads both states: `window=756 sidebar=320` under a 2x scale is exactly
the case that used to be zero.
- **The sidebar's button is the only input; `AdwOverlaySplitView:show-sidebar` is an output.** The split
view writes that property *itself* — at startup, and when it collapses — with no user involved, so reading
it back as the user's wish is wrong: doing that made connecting a source leave the sidebar shut, because
the widget's own `false` was recorded as the user asking for it. A `GBinding` from the property to the
toggle is the same mistake in one line, and it cannot be told from a real change by a "is this our own
write" flag either, since the toggle's signal may fire after the write returns. Instead `App::sidebar_wanted`
holds the wish (written only by `on_sidebar_toggled`, guarded by `App::sidebar_syncing` around our own
`set_active`), `update_side_visibility()` is the single place that turns that wish plus "are there signals"
into the panel's *and* the button's state together, and `tick()` re-asserts them when the widget disagrees —
which is also why a hide *gesture* does not stick: the panel stays where the button says it is. The
no-signals case must leave the wish alone, or a source connecting later finds a sidebar the user never closed.
- **`AdwOverlaySplitView:sidebar-width-fraction` is a 0..1 fraction of the window, whatever
`sidebar-width-unit` says.** Setting a pixel count there (320) is a `GObject` critical, and it silently
falls back to 0.25 — which reads as "the sidebar is mysteriously 560 px wide" on a wide window with no
+17 -6
View File
@@ -1046,19 +1046,30 @@ library measures and draws the rect in tests of its own.
## `[sidebar-probe]`
```
[sidebar-probe] beside window=756 sidebar=320 collapsed=1
[sidebar-probe] overlaid window=756 sidebar=320 collapsed=1
[sidebar-probe] beside window=756 sidebar=320 collapsed=1 shown=1 button=1(sens=1)
[sidebar-probe] regained window=756 sidebar=320 collapsed=1 shown=1 button=1(sens=1)
```
`UDPSCOPE_PROBE_SIDEBAR_MS` reads the sidebar's width in both of the split view's states: beside the plot,
and **overlaid** on it (`collapsed`, which is the state a window with no room beside the plot produces).
`UDPSCOPE_PROBE_SIDEBAR_MS` reads the sidebar in the two states a window can put it in, and against a hide
that did not come from the toolbar: `beside` first, then the sidebar is forced into the **overlaid** state
(`collapsed`, which is what a window with no room beside the plot produces), then the split view's own
`show-sidebar` is **turned off behind the button's back** — an overlay swipe, or the widget hiding itself —
and `regained` reads 400 ms later.
- **`sidebar` must never be 0.** It was, for every window narrower than the 940 px the divider used to sit
at, and the pane could only be found by dragging the divider back — the report this reading exists for.
It must read its own width (320) in *both* states: `collapsed` changes where the sidebar is, never how
wide it is.
- `collapsed=1` on the `beside` line means the window is narrower than `kSidebarRoom` (900), which is the
expected reading on a scaled display, where the logical width is half what the screen looks like.
- **`shown` and `button` must agree, always.** They are the panel and the toolbar button, and a button that
says "shown" over a hidden panel is the report this reading exists for. `shown=1 button=1` on `regained`
is also the rule that a hide not asked for through the button does not win.
- **`sens=0` with no source is correct.** The button is insensitive and un-pressed while there is nothing to
put in the sidebar; `button=0` is the button telling the truth about the panel rather than the panel
having forgotten the wish. A source reconnecting must give `shown=1 button=1` again — that is the reading
a `wanted` that got eaten by the no-signals frame shows as `shown=0 button=0(sens=1)`, which is the
regression.
- `collapsed=1` means the window is narrower than `kSidebarRoom` (900), which is the expected reading on a
scaled display, where the logical width is half what the screen looks like.
## `[prefs-probe]`