ui: the signal list is the signals slide-over page, opened by a + badge or g

This commit is contained in:
Martino Ferrari committed 2026-11-03 00:54:25 +01:00
1 parent e36f2a202b
commit 0dd235e41c
11 files changed
+98 -75

No files matched your search

+4 -3
View File
@@ -229,8 +229,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
replaced an `AdwViewSwitcher` over an `AdwViewStack`, whose four text tabs did not fit 320 px (and a source
can add more); a source kind's own panels are slide-over pages, not dock sections. The reason is the one
this trap teaches: sections competing for one 320 px column each left every one of them short of room, and
a scroller gives each its natural height. `m` and `g` scroll the dock to the cursors and to the signal list
(`dock_show_section()`). A *feature's* on/off is no longer a visibility at all -- the
a scroller gives each its natural height. `m` scrolls the dock to the cursors (`dock_show_section()`); the signal list is
not a dock section but the `signals` slide-over page, which `g` and the `+` badge open. A *feature's* on/off is no longer a visibility at all -- the
**cursors'** toggle sets its pane's **body** insensitive -- the readout has nothing to show with the
instruments off -- and the *body* and not the pane, because the pane's heading carries its own switch
(see the next bullet). The **trigger's body is deliberately never disabled**: a trigger is configured
@@ -643,7 +643,8 @@ The `Help` submenu, the contextual help's "?" and the guided tour, and the traps
and in a view with a `page_stack` a stop that names a page is kept when the stack is mapped -- a wrong
page leaves the stack mapped, so the page switch reveals it, while a collapsed pane leaves it unmapped
too and the stop is dropped. So the dock's stop is dropped in a window narrow enough to overlay it, and
the main window's tour is four steps there (`[toursurface] collapsed: ... steps=4`). The filter
the main window's tour is five steps there (`[toursurface] collapsed: ... steps=5`: the signals stop names
the `signals` slide-over page, which does not depend on the dock either). The filter
lives in `tour_begin()`, not in each window's `start_tour()`, so the two windows cannot drift
into two rules.
- **The chooser's tour needs an overlay over its *whole* window, and `AdwWindow`'s content is
+14 -14
View File
@@ -969,8 +969,8 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
slide); the `on_page` hook is what opens the page for the trigger stop and closes the panel for every
stop that names none. The lines:
- `[toursurface] before: slideover=none signal_list_mapped=1 running=1` -- the state a first run starts
in (panel closed, dock on screen); the auto-started walk is dismissed.
- `[toursurface] before: slideover=none signal_list_mapped=0 running=1` -- the state a first run starts
in (panel closed, dock on screen; the signal list is a slide-over page, so unmapped); the auto-started walk is dismissed.
- `[toursurface] on trigger: slideover=trigger trigger_body_mapped=1 side_box_mapped=1` -- a frame after
the page was opened: the dock is still on screen under the panel (they are not alternatives).
- `[toursurface] tour: visible=1 steps=6 step=0 trigger_stop=1 signals_stop=1` -- the plan: **six
@@ -978,8 +978,8 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
- `[toursurface] step=N/6 title='…' slideover=<page> target_mapped=D` -- one per stop, in order: *The
plot*, *The badges*, *The trigger*, *The dock*, *The signals*, *The status line*. **Step 1 reads
`slideover=none` although the probe started with the panel open** (a stop with no page closes it),
**step 3 reads `slideover=trigger`, and step 4 reads `slideover=none` again**; every `target_mapped`
is 1.
**step 3 reads `slideover=trigger`, step 4 reads `slideover=none` again, and step 5 (the signals)
reads `slideover=signals`**; every `target_mapped` is 1.
- `[toursurface] stop=N/6 visible=1 slideover=… spot=[…] raw=[…] target=[…] contains=1 inside=I card=[…] clear=C` -- the
`tour_geometry_line()` the helpui probe uses, one per stop, read a frame after the stop was entered
(the spotlight is withheld until the panel has finished sliding in, `page_settled`): **`contains=1` on
@@ -993,13 +993,13 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
- `[toursurface] restarted at trigger: running=1 slideover=trigger` then
`[toursurface] skipped: running=0 slideover=none` -- the walk parked on the trigger stop and dismissed
(the Skip/Esc route): **`slideover=none` after the dismissal** is the tour not leaving a panel behind.
- `[toursurface] collapsed: side_box_mapped=0 steps=4 trigger_stop=1 signals_stop=0 side_stop=0` -- the
- `[toursurface] collapsed: side_box_mapped=0 steps=5 trigger_stop=1 signals_stop=1 side_stop=0` -- the
filter with the dock put away (`sidebar_wanted` off; `update_side_visibility()` re-asserts it, so this
is how a probe puts the dock away for good): the dock's two stops are dropped, **and the trigger stop is
kept** because the slide-over does not depend on the dock -- four stops remain (plot, badges, trigger,
status).
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: dock 0->243 (cursors_y=243 reach=2256 at_cursors=1) | g: dock 243->0 at_top=1 | … | esc: opened=1 handled=1 closed=1 idle_handled=0 | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` returns, so its pair reads `243->0`), **`refused` must be 1**, and the `esc:` reading must be `opened=1 handled=1 closed=1 idle_handled=0`.
The `m`/`g` readings are the dock scroller's vertical adjustment before and after (the probe pads the dock to 3000 px so there is something to scroll, and undoes it at the end): **`m` must land on `cursors_y`, the cursors panel's y in the dock (`at_cursors=1`), and `g` must return to 0 (`at_top=1`)**, which is what says those two keys scroll to a section rather than toggling a pane; a `reach` of 0 would mean the dock fit its viewport and the probe's padding was lost; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. **The `esc:` reading goes through `on_app_key_pressed()` with `GDK_KEY_Escape`**, the handler the window's key controller calls, and not through `slideover_escape()`: the probe opens the `trigger` slide-over page (`opened=1`), sends the key (`handled=1`: it was consumed, and `closed=1`: the page is gone), then sends it again with nothing open (`idle_handled=0`: Escape is left alone when there is nothing for it to close, so a dialog or popover under it still gets it). `handled=0` or `closed=0` is the slide-over's Escape never reaching the panel from the window's key path -- a handler that returns before the slide-over test, or a controller in the wrong phase -- which the `[slideover] esc_closes=1` reading (a direct call to `slideover_escape()`) cannot see. **Limit of the `m`/`g` readings:** the padding is 3000 px at the end of the page, so `reach` is non-zero on any window shorter than that and the key has somewhere to scroll, but it does not push the cursors panel out of the viewport -- on a window whose dock viewport is taller than `cursors_y` (a tall window) the section is already in view at offset 0 and `m` still scrolls to it; `at_cursors=1` is the key landing the section's top at the viewport's top, which is the reading, not "the section was hidden before". The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `<key>: <widget-description>: <before>-><after>` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values).
is how a probe puts the dock away for good): the dock's own stop is dropped, **and the trigger and
signals stops are kept** because the slide-over does not depend on the dock -- five stops remain (plot,
badges, trigger, signals, status).
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: dock 0->243 (cursors_y=243 reach=2256 at_cursors=1) | g: page=1 dock_still=1 closed=1 | … | esc: opened=1 handled=1 closed=1 idle_handled=0 | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` is not a pair: it opens the `signals` slide-over page and a second press closes it), **`refused` must be 1**, and the `esc:` reading must be `opened=1 handled=1 closed=1 idle_handled=0`.
The `m`/`g` readings are the dock scroller's vertical adjustment before and after (the probe pads the dock to 3000 px so there is something to scroll, and undoes it at the end): **`m` must land on `cursors_y`, the cursors panel's y in the dock (`at_cursors=1`), and `g` must read `page=1 dock_still=1 closed=1`** (the `signals` page opened, the dock's scroll untouched, a second press closing it), which is what says `m` scrolls to a section and `g` opens the list's page rather than toggling a pane; a `reach` of 0 would mean the dock fit its viewport and the probe's padding was lost; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. **The `esc:` reading goes through `on_app_key_pressed()` with `GDK_KEY_Escape`**, the handler the window's key controller calls, and not through `slideover_escape()`: the probe opens the `trigger` slide-over page (`opened=1`), sends the key (`handled=1`: it was consumed, and `closed=1`: the page is gone), then sends it again with nothing open (`idle_handled=0`: Escape is left alone when there is nothing for it to close, so a dialog or popover under it still gets it). `handled=0` or `closed=0` is the slide-over's Escape never reaching the panel from the window's key path -- a handler that returns before the slide-over test, or a controller in the wrong phase -- which the `[slideover] esc_closes=1` reading (a direct call to `slideover_escape()`) cannot see. **Limit of the `m`/`g` readings:** the padding is 3000 px at the end of the page, so `reach` is non-zero on any window shorter than that and the key has somewhere to scroll, but it does not push the cursors panel out of the viewport -- on a window whose dock viewport is taller than `cursors_y` (a tall window) the section is already in view at offset 0 and `m` still scrolls to it; `at_cursors=1` is the key landing the section's top at the viewport's top, which is the reading, not "the section was hidden before". The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `<key>: <widget-description>: <before>-><after>` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values).
- `[color] '<name>' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `<span foreground="…">` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit.
- `[reseq-probe] reseq=N late=N stale=N overflows=N holes=N hole_slots=N backfilled=N
gaps_refused=N open_holes=N bands=N bands_repaired=N nan_slots=N scanned=N trigger_in_hole=D`
@@ -1819,14 +1819,14 @@ and `regained` reads 400 ms later.
## `[side-tab-probe]`
```
[side-tab-probe] section=signals accums=4 baseline recorded (measure at scroll 2732 of 2732, outside_at_top=1)
[side-tab-probe] section=signals rows=4 accums=4 advanced=4 min_n=18
[side-tab-probe] section=cursors accums=4 baseline recorded (measure at scroll 2732 of 2732, outside_at_top=1)
[side-tab-probe] section=cursors rows=4 accums=4 advanced=4 min_n=18
```
`UDPSCOPE_PROBE_SIDE_TAB_MS` (the variable and the tag keep their pre-dock names; the function is `dock_section_probe`) is the one reading for the rule that **the statistics accumulator keeps running
while the Measure section is out of view**. It scrolls the dock *to* the Measure section (`dock_show_section("measure")`), forces one pass so there is a baseline, then scrolls back to Signals and reads again two seconds later. To make the scroll observable the probe pads `dock_page` **and the signal list** to 3000 px each (probe only, both undone in the report), and it re-arms once after 400 ms so the first reading has the layout.
while the Measure section is out of view**. It scrolls the dock *to* the measurements (`dock_show_section("measure")` lands on the cursors panel, the dock's top, so the probe then moves to the measurements' heading itself), forces one pass so there is a baseline, then scrolls back to the top (the cursors) and reads again two seconds later. To make the scroll observable the probe pads `dock_page` **and the cursors panel** to 3000 px each (probe only, both undone in the report), and it re-arms once after 400 ms so the first reading has the layout.
- **`outside_at_top=1` is the precondition**: with the dock at offset 0, the Measure section (the cursors panel, where `dock_show_section("measure")` lands) starts at or below the viewport's bottom edge, so the "away from the section" state really has the measurements out of view and `advanced=4` says something. `outside_at_top=0` means the probe's own set-up failed and the run proves nothing -- before the signal list was padded it read `0` on the documented window, where the section's top is 243 px down and the dock's viewport is taller than that. **The limit is the 3000 px:** the guarantee holds for a dock viewport shorter than the padding (any real window); a viewport taller than the signal list's padding, about 3000 logical px, would read `0` again.
- **`outside_at_top=1` is the precondition**: with the dock at offset 0, the Measure section (the measurements' heading, below the padded cursors panel) starts at or below the viewport's bottom edge, so the "away from the section" state really has the measurements out of view and `advanced=4` says something. `outside_at_top=0` means the probe's own set-up failed and the run proves nothing -- unpadded, the section's top sits a short way down and the dock's viewport is taller than that. **The limit is the 3000 px:** the guarantee holds for a dock viewport shorter than the padding (any real window); a viewport taller than the cursors panel's padding, about 3000 logical px, would read `0` again.
- **`advanced=4` is the assertion**: every accumulator gained passes while its section was out of view, and
`min_n` (the smallest `n` seen) must be non-zero too. **`advanced=0` is the defect**: something gated the
+3 -3
View File
@@ -424,8 +424,8 @@ precedent, not an exception.
phases: `phase=0`, `phase=1` (after pause) and `phase=2` (after Reset). Needs
`UDPSCOPE_ENABLE_ALL_MS` ahead of it and `UDPSCOPE_PROBE_MEASURE_MS` to add measurements first.
- `UDPSCOPE_PROBE_SIDE_TAB_MS=3000` with `UDPSCOPE_EXIT_AFTER_MS=11000` — the measurements in the
dock's Measure section, a forced pass, then the dock scrolled back to the signal list (the Measure
section out of view) and read again two seconds later. The probe pads the dock page and the signal list to
dock's Measure section, a forced pass, then the dock scrolled back to its top (the
measurements out of view) and read again two seconds later. The probe pads the dock page and the cursors panel to
3000 px each so the Measure section really starts below the viewport (`outside_at_top=1`) on any real window. **`advanced=4`
is the assertion** (every accumulator gained passes while its section was out of view); `advanced=0` is the
defect where the accumulation is gated on the visible section, which would leave a gap in min/max/avg for
@@ -656,7 +656,7 @@ as distinct from the fault injection below, which exercises ingest:
(and a dismissal on the trigger stop) leave none. It then puts the dock *away* and starts the tour again:
the dock's stops are dropped while the trigger stop, which does not depend on the dock, stays.
`[toursurface] stop=3/6 … slideover=trigger … contains=1` and
`[toursurface] collapsed: side_box_mapped=0 steps=4 trigger_stop=1 signals_stop=0` are the assertions; its
`[toursurface] collapsed: side_box_mapped=0 steps=5 trigger_stop=1 signals_stop=1` are the assertions; its
lines are in [probe-readings.md](probe-readings.md). Run it alone with
`UDPSCOPE_EXIT_AFTER_MS=9000 UDPSCOPE_EXIT_DUMP=1` against a live source.
- `UDPSCOPE_PROBE_DEFAULTS_MS` — opens **Calibrate** and reads every spin button in it and in the