ui: the tour and the contextual help follow the badge bar, the slide-over and the dock

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 20:46:59 +01:00
1 parent 4b65197f56
commit 2826042156
5 files changed
+254 -119

No files matched your search

+60 -38
View File
@@ -489,7 +489,7 @@ Two lines of the self-check output are assertions in disguise:
column and says there is one scroller and no stack. These are captured on the tick or at the exit probe:
the dump runs after the window is destroyed. A source with extra panels adds *no* dock section: its panels
are slide-over pages (`source-<id>`), so the line reads the same on the E-1608 and the probe kind.
- `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=5 step=0 auto=1 requested=0` (in the exit
- `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=6 step=0 auto=1 requested=0` (in the exit
dump, beside `dock:`) — the help affordance layer as the last tick found it. **`hints=` must equal
the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window
itself, and `math` only while the math editor is open (its heading lives in a builder that is rebuilt
@@ -534,14 +534,18 @@ Two lines of the self-check output are assertions in disguise:
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). The writer chains to
`g_log_writer_default()`, so every message still reaches stderr exactly as before and the documented
baselines are read the same way.
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… side_stack=… window_button=… status_label=… header_bar=… plot=…` (in the exit
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… badge_bar=… dock=… window_button=… status_label=… header_bar=… plot=…` (in the exit
dump, beside `help layer:`) — the **window-coordinate** rectangle of each surface the help's annotated
figures label, `x,y,width,height`, or `-1,-1,0,0` for a surface that is **not on screen**. It is
measured on the tick while the window is alive (nothing in the dump may call `gtk_widget_*` — see
[verification.md](verification.md)) and with `gtk_widget_is_visible()`, not `get_visible()`: a widget
under a hidden parent — `trigger_summary` while the pane is in *configuration* mode, the Measure page
while the Signals tab is showing — keeps its own flag true and reads `0x0`, and a crop box drawn from
that is a box over the wrong thing. The figures are produced by cropping a real screenshot, so these
[verification.md](verification.md)) and with `gtk_widget_is_visible()` **and** `gtk_widget_get_mapped()`,
not `get_visible()`: a widget under a hidden parent — `trigger_summary` while the pane is in
*configuration* mode, and **every `trigger_*` surface while the slide-over is closed** (a revealer
unmaps its child without touching its visible flag) — keeps its own flag true and reads `0x0`, and a crop
box drawn from that is a box over the wrong thing. So the three `trigger_*` rectangles read real numbers
only while the `trigger` page is open (the exit probe has it open; `UDPSCOPE_PROBE_HELPUI_MS` opens it for
its figures legs), and `-1,-1,0,0` otherwise. `badge_bar` is the badge row under the plot and `dock` the
right-hand column (these replaced `side_stack`, the old sidebar stack). The figures are produced by cropping a real screenshot, so these
numbers **are** the crop boxes; their values depend on the window's size, which under the documented
`xvfb-run` is the window's 756x894 minimum (the 640x480 screen is smaller than the content), so a run
on a larger screen reads different numbers for the same panels. `trigger_summary` reading
@@ -878,7 +882,7 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
every row then reports `h=0`. It turns autohide off for its own measurement — a popover dismisses
itself when it loses the grab, and `xvfb-run` has no window manager to hand it one — and restores
it afterwards.
- `[helpui] auto_started=1 visible=1 steps=5 step=0 (first run)` (stderr, `UDPSCOPE_PROBE_HELPUI_MS`) —
- `[helpui] auto_started=1 visible=1 steps=6 step=0 (first run)` (stderr, `UDPSCOPE_PROBE_HELPUI_MS`) —
the help affordance layer end to end, in ten steps 300-400 ms apart (every reading is an
*allocation*, and a step that read its own predecessor's geometry would read a rectangle from before
the frame that placed it). The line above is the **first-run auto-start**, read before the probe
@@ -910,21 +914,25 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
affordances are *gone*, not greyed, so anything else is a row that hid its buttons instead of
removing them. The count comes back to the same twelve, which is the other half — a toggle that
removed and could not restore is a one-way switch.
- `[helpui] tour started from the probe: visible=1 steps=5 step=0` — the tour started the way
`Ctrl+T` starts it. `steps=5` is the plan: the table's five steps whose surface is on screen or
selectable into it (`gtk_widget_get_mapped()` -- a wrong *page* is kept, because selecting its
page reveals it, while a *collapsed pane* is still dropped; see the surface bullets in
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
- `[helpui] step=1/5 visible=1 slideover=none spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
- `[helpui] tour started from the probe: visible=1 steps=6 step=0` — the tour started the way
`Ctrl+T` starts it. `steps=6` is the plan: the table's six stops (plot, badges, trigger, dock,
signals, status) whose surface is on screen or can be opened into view. A stop that names a
slide-over page is always kept (the `on_page` hook opens it whatever the dock is doing), a target
that is mapped is kept, and a stop in a *collapsed dock* is dropped (`gtk_widget_get_mapped()`; see
the surface bullets in [invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
- `[helpui] step=1/6 visible=1 slideover=none spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
— **in window coordinates**, the frame a screenshot is cropped in, with the slide-over's open page
(`none`, or a page id) beside it; see `[toursurface]` below.
(`none`, or a page id) beside it; see `[toursurface]` below. The `target=` rectangle is the part of the
target that is *on screen* (intersected with the scrolled windows above it, `tour_target_bounds()`): a
form taller than the panel's scroller on a short window is spotlit down to the scroller's edge and no
further.
**`contains` must be 1** on every step: the spotlight is the target's own rectangle grown three
pixels. `clear` is the card missing the target, and it is 1 wherever the window has room for the
card beside or above/below the hole — at the documented run's window (see `help figures:` below)
the plot's step is *the whole content*, because at 756 px the split view overlays the side panel
rather than putting it beside the plot, so `clear=0` there is the geometry and not a placement
bug. The side panel's step reads `clear=1`.
- `[helpui] after Next: step=2/5 visible=1`, `[helpui] after Back: step=1/5 visible=1`,
bug. The badges step reads `clear=1`.
- `[helpui] after Next: step=2/6 visible=1`, `[helpui] after Back: step=1/6 visible=1`,
`[helpui] after Skip: visible=0 dims_hidden=1 spot_hidden=1 card_hidden=1 controllers=12 (was 12)
click_gestures=2` — the three controls. **`controllers=` equal to `(was N)` is the "no lingering
input grab" reading**: a tour of ordinary overlay widgets takes no grab at all, and a count that
@@ -935,7 +943,9 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
trigger pane's modes, by clicking its [edit] button. This exists because one of the help's figures
is the *locked* pane's summary grid (`trigger_summary`), which in configuration mode is not on
screen at all and reads `-1,-1,0,0`; the two lines give the geometry agent both states' numbers
rather than leaving them to find the mode. `trigger_panel`'s height differs between them (508 px in
rather than leaving them to find the mode. The probe opens the slide-over's `trigger` page for these
legs (and for the collapsed-search leg after them) and closes it at the end: with the panel shut
every `trigger_*` reads `-1,-1,0,0`. `trigger_panel`'s height differs between them (532 px in
configuration mode against 72 locked — the body is replaced by the summary), which is the check
that the mode really moved.
- `[helpui] collapsed search: expanded=0 mapped=1 mapped_header=1 hint=12x12` (the last step, in
@@ -945,28 +955,40 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
read `mapped=0` (and `0x0`), which is exactly why the header box exists (`search_head` in
`src/ui/trigger-panel.ui`) and why the hint joins it as its last child. `mapped_header=` is the
header box beside it, and `hint=WxH` is the same 12x12 the layer's own line reads.
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) -- a stop that stands on a surface it is
not on, entered from the state it is not in. Since the sidebar's tabs became the dock the probe opens the
`trigger` slide-over page instead of switching a tab, starts the window's tour and walks it a stop per tick
(400 ms). **The tour's own rework for the slide-over is Task 5's**: until its page hook exists the tour
has no page stack, so a stop whose target is on a slide-over page is dropped, and the lines below read the
dock only. The lines:
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) -- a stop that opens a surface it is not
on, entered from the state it is not in. The probe opens the `trigger` slide-over page, starts the
window's tour from there and walks the whole table a stop per tick (400 ms, past the panel's 120 ms
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
(the dock is on screen), then the auto-started walk is dismissed.
- `[toursurface] on trigger: slideover=trigger signal_list_mapped=1 side_box_mapped=1` -- a frame after the
page was opened: the dock is still on screen under the slide-over (they are not alternatives).
- `[toursurface] tour: visible=1 steps=N step=0 signals_stop=1` -- **`signals_stop=1` is the filter's
reading**: the signal list is in the dock, which is mapped, so its stop is in the plan.
- `[toursurface] step=N/M slideover=<page> target_mapped=D[ target=signal_list]` -- one per stop;
**the `signals` stop reads `target_mapped=1`**.
- `[toursurface] signals stop=N/M visible=1 slideover=… spot=[…] target=[…] contains=1 card=[…] clear=1` --
the `tour_geometry_line()` the helpui probe uses: **`contains=1` is the assertion** that the spotlight
and the card are on the list.
- `[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0 side_stop=0` -- the other half of
the filter, read: with `sidebar_wanted` off (`update_side_visibility()` re-asserts it, so this is how a
probe puts the sidebar away for good) the split view unmaps the column and the stops that need the
dock are dropped while the page-less ones stay. This is what says a *collapsed pane* is dropped.
- `[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] 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
stops, the trigger stop and the signals stop both in it**.
- `[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.
- `[toursurface] stop=N/6 visible=1 slideover=… spot=[…] target=[…] contains=1 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
every stop is the assertion** that the spotlight is on the target. `clear=0` is read as geometry, not
as a placement bug, on the plot stop (the target is the whole content) and on the trigger stop at the
documented 756 px window (the panel is 360 px of it, so the card has no side wide enough and goes flush
left over the plot, overlapping the panel's edge by a few pixels).
- `[toursurface] finished: running=0 slideover=none` -- the last Next ended the walk and **the panel is
closed with it**.
- `[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
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 | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` returns, so its pair reads `243->0`) and **`refused` must be 1**.
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 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.
+7 -5
View File
@@ -639,11 +639,13 @@ as distinct from the fault injection below, which exercises ingest:
second and a half, and that start *writes* `tour_seen` to whichever config directory the run is
using, which is why the second run against the same one reads `auto=0`.
- `UDPSCOPE_PROBE_TOUR_SURFACE_MS` — the tour's *surface* switch: it opens a slide-over page (`trigger`),
starts the window's tour, and walks it a stop per tick to the signals stop, reading the sidebar's
page and the spotlight/card where the walk arrives. It then puts the sidebar *away* and starts the
tour again, which is the same filter's other case (a collapsed pane drops the stops a page switch
could have revealed). `[toursurface] signals stop=4/5 … slideover=none … contains=1` and
`[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0` are the two assertions; its
starts the window's tour from that state and walks all six stops a stop per tick, reading the
slide-over's open page and the spotlight/card at each. The trigger stop must read `slideover=trigger` and
the next `slideover=none`; the first stop closes the panel the probe left open, and the end of the walk
(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
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