ui: the sidebar's tabs become one dock; Measure and Signals by keyboard section; session keeps the dock's state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
31ff778e07
commit
4b65197f56
19 files changed
+421
-419
No files matched your search
@@ -479,22 +479,18 @@ Two lines of the self-check output are assertions in disguise:
|
||||
substitutes a missing-image placeholder, the button still works, and every other line of the
|
||||
dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this
|
||||
is the only check that it names something that exists.
|
||||
- `switcher: policy=wide pills=4 labels=4 images=0 pages=4 size=308x34` (in the exit dump, beside `panels:`) — the
|
||||
sidebar tabs as the switcher actually draws them, and they are **words**: `labels` counts *mapped labels
|
||||
with text*, so `pills=labels=pages` says every page has a visible title. **`images` must be `0`**: it
|
||||
counts *mapped* `GtkImage`s inside the switcher. `AdwViewSwitcherButton` builds an icon in every tab, and
|
||||
a page with a title and no `icon-name` gets `image-missing` -- the `[x]` placeholder; the tabs hide it
|
||||
(`keep_tabs_icon_free()`, which also covers pages `build_source_panels()` adds). A nonzero `images`
|
||||
names a tab still drawing an icon or its placeholder (`images=pages` is the unfixed state), and
|
||||
`pills`/`labels` never saw it. Read it on a source with extra pages (`--source probe` under
|
||||
`UDPSCOPE_PROBE_SOURCE_KINDS`) as well as on UDPS. `pages` is read from the stack
|
||||
(2 for UDPS, 3 for the probe source, 4 for the E-1608 with its Analog out and Digital I/O tabs). A
|
||||
`labels` below `pills` is a page added with an empty title; `pills=0` means nothing is mapped, which
|
||||
makes the rest of the line meaningless rather than a pass. `policy=wide` is read rather than assumed.
|
||||
`size=` is the bar's own size: a text tab costs about 34 px of height, and a width of 308 shows the
|
||||
four tabs fit the 320 px column.
|
||||
- `dock: shown=1 sensitive=1 w=320 scroller_pages=1` (in the exit dump, beside `panels:`) -- the sidebar's dock
|
||||
as the last tick found it. It replaces the old `switcher:` line: there is no tab switcher, the sidebar is
|
||||
one scroller (`dock_scroller`) holding the signal list, the cursors and the measurements. **`shown=1`
|
||||
always** (the dock is on screen from the start; `shown=0` only when the user hid it with the Dock button,
|
||||
which a probe does deliberately); **`sensitive=0` only before the stream has signals** ("disable, do not
|
||||
hide") -- a run against a port with no sender reads `shown=1 sensitive=0`, a normal run `sensitive=1`;
|
||||
`w=` is the sidebar's own width (320); **`scroller_pages=1`** counts the scrollers directly under the dock
|
||||
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
|
||||
dump, beside `switcher:`) — the help affordance layer as the last tick found it. **`hints=` must equal
|
||||
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
|
||||
on every opening, so it is not one of the window's own). Four of the eleven are not a pane's *heading*
|
||||
@@ -538,7 +534,7 @@ 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_switcher=… 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=… side_stack=… 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
|
||||
@@ -919,9 +915,9 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
||||
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 side_tab=signals spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
|
||||
— **in window coordinates**, the frame a screenshot is cropped in, with the sidebar's page beside
|
||||
it (a stop the walk had to change the page for is readable as such; see `[toursurface]` below).
|
||||
- `[helpui] step=1/5 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.
|
||||
**`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)
|
||||
@@ -949,34 +945,30 @@ 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 page it is
|
||||
not on, entered from the page it is not on. It puts the sidebar on **Measure**, starts the window's
|
||||
tour, and walks it a stop per tick (400 ms), because a stack allocates the new page's widgets on
|
||||
the frame *after* the switch — so each reading is of the page the step left standing, not of the
|
||||
one the switch has just left behind. The lines:
|
||||
- `[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] before: side_tab=signals signal_list_mapped=1 running=1` — the state a first run
|
||||
starts in (a fresh config's sidebar is on Signals), then the auto-started walk is dismissed.
|
||||
- `[toursurface] on measure: side_tab=measure signal_list_mapped=0 side_stack_mapped=1` — a frame
|
||||
after the switch: **`signal_list_mapped=0` beside `side_stack_mapped=1`** is the whole premise,
|
||||
the list is genuinely off screen while the stack that holds it is on screen.
|
||||
- `[toursurface] tour: visible=1 steps=5 step=0 signals_stop=1` — **`signals_stop=1` is the filter's
|
||||
reading**: a stop whose target is *not mapped* is still in the plan because it names a page the
|
||||
stack can select. `steps=5` is the full table.
|
||||
- `[toursurface] step=N/5 side_tab=<page> target_mapped=D[ target=signal_list]` — one per stop. The
|
||||
page moves from `measure` to `signals` at the `trigger` stop (the first stop whose target is on
|
||||
the Signals page) and stays there; **the `signals` stop reads `target_mapped=1`**.
|
||||
- `[toursurface] signals stop=4/5 visible=1 side_tab=signals spot=[…] target=[…] contains=1
|
||||
card=[…] clear=1` — the `tour_geometry_line()` the helpui probe uses, read on the frame *after*
|
||||
the switch: **`side_tab=signals` with `contains=1` is the assertion** that the spotlight and the
|
||||
card are on the list, not on the page the walk came from.
|
||||
- `[toursurface] collapsed: side_stack_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, the stack with
|
||||
it, and the two stops that need the sidebar are dropped while the page-less ones stay. This is
|
||||
what says a *wrong page* and a *collapsed pane* are not the same case.
|
||||
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: tab signals->measure | g: tab measure->signals | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** and **`refused` must be 1**.
|
||||
The `m`/`g` pairs are the *tab name* before and after, which is what says those two keys select a tab rather than toggling a pane; `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).
|
||||
- `[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.
|
||||
- `[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.
|
||||
- `[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`
|
||||
@@ -1796,18 +1788,16 @@ and `regained` reads 400 ms later.
|
||||
## `[side-tab-probe]`
|
||||
|
||||
```
|
||||
[side-tab-probe] page=signals accums=4 baseline recorded
|
||||
[side-tab-probe] page=signals rows=4 accums=4 advanced=4 min_n=18
|
||||
[side-tab-probe] section=measure accums=4 baseline recorded (measure at scroll 243 of 2256)
|
||||
[side-tab-probe] section=signals rows=4 accums=4 advanced=4 min_n=18
|
||||
```
|
||||
|
||||
`UDPSCOPE_PROBE_SIDE_TAB_MS` is the one reading for the rule that **the statistics accumulator keeps running
|
||||
on the hidden page**. It switches *to* the Measure tab, forces one pass so there is a baseline, then switches
|
||||
away to Signals and reads again two seconds later -- with the measurements' page not current, so its widgets
|
||||
are unrealized and measure 0x0.
|
||||
`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 on a short window the probe pads `dock_page` to 3000 px (probe only), and it re-arms once after 400 ms so the first reading has the layout.
|
||||
|
||||
- **`advanced=4` is the assertion**: every accumulator gained passes while its page was off screen, and
|
||||
- **`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
|
||||
accumulation on the visible tab, so the readout would gain a gap in its min/max/avg for exactly as long as
|
||||
accumulation on the visible section, so the readout would gain a gap in its min/max/avg for exactly as long as
|
||||
the trigger was on screen. A `Gtk-CRITICAL` here is the other half of the same rule -- a label or an
|
||||
allocation read on the unrealized page.
|
||||
- The probe enables a signal **and assigns it to a plot** itself, both deliberately. A signal starts disabled
|
||||
@@ -1849,7 +1839,7 @@ kind, so a run without it never shows that kind. The `form` line appears once pe
|
||||
`drained=0` is a queue that `poll()` does not drain on some path; `after` greater than `before + 1` is a
|
||||
command run twice.
|
||||
|
||||
The UDPS run has no `source-` page in `side_stack`, because the UDPS kind has no panels.
|
||||
The UDPS run has no `source-` slide-over page, because the UDPS kind has no panels.
|
||||
|
||||
## `[e1608]`
|
||||
|
||||
@@ -2591,3 +2581,19 @@ The same probe then puts five measurements on the same spiked `sine` as **two wi
|
||||
- **`measure windows=2`** -- the five legs ask for corners `0, 50, 0, 0, 50` on one channel, which is two
|
||||
distinct windows. **`windows=1` means the corner is not part of the key**: the five collapsed onto one walk
|
||||
and two of them read a signal they did not ask for.
|
||||
|
||||
## `[dock-session]`
|
||||
|
||||
```
|
||||
[dock-session] hidden: wish=0 save_scheduled=1 collected=0 saved=1 loaded=1 read=0 applied=1 split_follows=1 ok=1
|
||||
[dock-session] shown: wish=1 save_scheduled=1 collected=1 saved=1 loaded=1 read=1 applied=1 split_follows=1 ok=1
|
||||
[dock-session] old file (side_tab only): loaded=1 dock_shown=1 default=1 ok=1
|
||||
```
|
||||
|
||||
`UDPSCOPE_PROBE_DOCK_SESSION_MS` reads the one thing the dock persists, the Dock button's state, as `dock = true|false` in the
|
||||
session file (it replaced `side_tab`). Each of the first two lines drives the toggle, then follows the wish through: `save_scheduled=1` is
|
||||
`on_sidebar_toggled` having scheduled a save (a user's wish that was not saved is lost at the next start), `collected` is
|
||||
`collect_layout()`'s `dock_shown`, `saved`/`loaded` the file round trip on a temp path, `read` the loaded value, `applied`
|
||||
the wish after `apply_layout()`, and `split_follows=1` the split view following it. **`ok=1` on all three lines is the assertion.** The third line
|
||||
loads a file as the previous version wrote it (`side_tab = "measure"`): it must load with no error and leave `dock_shown` at its default
|
||||
(`true`) -- the parser ignores unknown keys, and this is what says the old key still does not break a session. The probe writes only to a temp path.
|
||||
Reference in new issue
Block a user