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
@@ -36,7 +36,7 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*.
|
||||
`src/source/udps_*.cpp` (`grep -rn "udps_" src` outside those files is a leak of the old coupling).
|
||||
- The frame is **single-timebase**. A multi-timebase source (ABScope) will change the interface and
|
||||
the data path together; do not bend one frame to carry two clocks.
|
||||
- A kind's capability panels are pages of `side_stack`, named `source-<id>`. A disconnected source
|
||||
- A kind's capability panels are pages of the slide-over (`slideover_add_page`), named `source-<id>`. A disconnected source
|
||||
leaves them **insensitive, not hidden** (`apply_source_panels` in `src/ui_source.cpp`).
|
||||
- Session file naming for a non-UDPS kind: the pseudo-host is `<kind>-<identity>` (characters outside
|
||||
`[A-Za-z0-9._-]` become `_`) on port **1** (0 is rejected by the session-file parser), the kind is
|
||||
|
||||
@@ -50,7 +50,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
runs after the window is destroyed and asking GTK about a widget then is four criticals, which is
|
||||
how the first version of that line earned them. What stays in the menu is what acts on the *data*
|
||||
(Fit, Export, Configurations) and the one editor that is not a pane (the layout editor): the signal list
|
||||
is a *tab* of the sidebar, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text.
|
||||
is a section of the sidebar's dock, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text.
|
||||
- **Five of this app's icons are its own, and they are a `GResource` compiled into the
|
||||
binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a
|
||||
measurement ruler, and the stock names those buttons used to carry said something else
|
||||
@@ -61,9 +61,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is
|
||||
the Preferences window's icon.
|
||||
|
||||
The sidebar's **tabs have no icons any more**: two glyphs (`udpscope-tab-measure-symbolic`,
|
||||
The sidebar has **no tabs any more** (it is one dock): two tab glyphs (`udpscope-tab-measure-symbolic`,
|
||||
`udpscope-tab-signals-symbolic`, each a 32x16 pair) were drawn for them and judged not understandable
|
||||
enough, so the tabs are text and both SVGs were deleted. The *trigger's* own icon was redrawn to the
|
||||
enough, so the tabs were text, and both SVGs were deleted. The *trigger's* own icon was redrawn to the
|
||||
Signals glyph's picture at that time and kept it.
|
||||
- **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has
|
||||
none.** `src/shortcuts.cpp`'s strings are handed to `AdwShortcutsItem`, which parses them
|
||||
@@ -229,11 +229,13 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
instead of across. `gtk_widget_set_visible()` is what a layout honours, so anything that must not
|
||||
take room has to be hidden and not merely unrevealed; the revealer keeps the slide while the pane
|
||||
*is* open. The allocated width is how this was found -- the revealer's own measure said 0.
|
||||
**The sidebar is a tab stack now, and has no `GtkRevealer` left in it**: an `AdwViewSwitcher`
|
||||
above an `AdwViewStack`, each page a `GtkScrolledWindow` of its own (the two pages are different
|
||||
heights, so one shared scroller would leave a switch scrolled where the new page never asked to
|
||||
be). The reason is the one this trap teaches: five sections competing for one 320 px column left
|
||||
every one of them short of room. A *feature's* on/off is no longer a visibility at all -- the
|
||||
**The sidebar is one dock now, and has no `GtkRevealer` left in it**: a single `GtkScrolledWindow`
|
||||
(`dock_scroller`) holding the signal list, the cursors and the measurements one under another. It
|
||||
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
|
||||
**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
|
||||
@@ -241,23 +243,15 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
ordinary way to work. It used to go insensitive with the mode, which left the settings unreachable
|
||||
exactly when a user wanted to change them; `trigger panel sensitive …=1` is the regression guard, and a
|
||||
`0` there means the configuration has been locked out again. The
|
||||
trap itself does not go away with the widget: a stack's hidden page measures 0x0 too and is still
|
||||
given the leftover width, so **ask about state, not about size** carries over unchanged. Every
|
||||
allocation read in the sidebar is a debug capture, and each reads 0 on the page that is *not*
|
||||
current -- a change in what the reading means, not a defect, and no functional code may depend on
|
||||
those numbers.
|
||||
- **The sidebar's tabs are text: `AdwViewSwitcherPolicy.wide`, a title on every page, no icon.** The
|
||||
built-in pages get their titles in the `.ui` and a source kind's pages from `SourcePanel::title`
|
||||
(`adw_view_stack_add_titled`); the page's *name* still identifies it to `select_side_tab()` and the
|
||||
session file. An icon-only version existed (policy `narrow`, empty titles, the pills named in code by
|
||||
`name_side_tabs()`, because `narrow` still draws a label beside an icon); it was reversed because the
|
||||
glyphs were not understandable. A page added with an empty title is a blank pill, and
|
||||
`switcher: … pills=N labels=N images=0 pages=N` reads it as `labels` below `pills`. **No icon at all**
|
||||
is not what "no `icon-name`" gives you: `AdwViewSwitcherButton` always builds a `GtkImage` and falls
|
||||
back to `image-missing` (the `[x]` placeholder, which also squeezed the title to "Meas..."), and no
|
||||
property turns it off. `keep_tabs_icon_free()` (`src/ui_source.cpp`) hides every image in the switcher,
|
||||
re-hides one that is shown again, and re-runs on the stack's `items-changed` (connected *after* the
|
||||
switcher's own, so the new button exists) and on the switcher's `map`; `images=0` is the reading.
|
||||
trap itself does not go away with the widget: a section scrolled out of view is still laid out, but
|
||||
**ask about state, not about size** still holds for anything a tick reads.
|
||||
- **The dock is shown but insensitive until the stream has signals ("disable, do not hide").**
|
||||
`update_side_visibility()` and the tick's per-frame block set the column's visibility from the user's wish
|
||||
alone (`sidebar_wanted`) and the dock's *sensitivity* from `have_signals`; the toolbar's Dock button stays
|
||||
sensitive throughout. The wish is persisted (`dock = true|false` in the session file) and a change of it
|
||||
schedules a save. There is no tab icon to keep clear any more: the `keep_tabs_icon_free()` workaround for
|
||||
`AdwViewSwitcherButton`'s placeholder `[x]` image went with the switcher, and `dock:` in the dump
|
||||
replaces the `switcher:` line.
|
||||
- **An *overlaid* sidebar covers the plot, so a drag out of it has nowhere to land.** `collapsed` in an
|
||||
`AdwOverlaySplitView` means the sidebar sits *on* the plot rather than beside it: the pointer is over the
|
||||
list, never over a drop target, and a signal can be dragged but not dropped. `row_drag_sidebar()` hides
|
||||
@@ -278,7 +272,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
`sync_panel_switches()` re-reads each pair from the toolbar's state after any change — so clicking
|
||||
either end moves both, and `panel switches: trigger=1/1 cursors=1/1` is the reading. Two traps:
|
||||
a `GtkSwitch` emits `notify::active` for a *programmatic* write as well, so the sync holds a guard
|
||||
(`App::panel_switch_sync`) or the program's own write comes back as a click; and the switcher must
|
||||
(`App::panel_switch_sync`) or the program's own write comes back as a click; and the switch must
|
||||
stay *out* of what the pane disables, because a control inside an insensitive parent cannot be
|
||||
clicked — a switch that greyed out with its own pane could never turn it back on. That is why the
|
||||
sensitive target is the pane's **body** (`trigger_body`, `cursors_body`) and not the pane.
|
||||
|
||||
@@ -23,10 +23,11 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
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 —
|
||||
`set_active`), `update_side_visibility()` is the single place that turns that wish into the panel's visibility
|
||||
and "are there signals" into the dock's *sensitivity* (the button stays sensitive), and `tick()` re-asserts both 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.
|
||||
The wish is the session's `dock` key: a change of it (not a program's own write, and not a restore) schedules a save.
|
||||
- **`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
|
||||
|
||||
@@ -30,7 +30,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
not freeze a colour that is meant to follow the signal list), **the math channels with their
|
||||
graphs**, **the measurements pane**, and **`measure_stats`** — the measurements statistics
|
||||
toggle; written as a bare top-level boolean (`measure_stats = true/false`), defaults to `false`
|
||||
when absent. Signal identity
|
||||
when absent, and **`dock`** -- the Dock button's state (the user's last word on it, `sidebar_wanted`),
|
||||
a bare top-level boolean written where the sidebar's `side_tab` used to be, default `true`. The parser
|
||||
ignores unknown keys, so a file written before the tabs became a dock (`side_tab = "measure"`) still loads
|
||||
and simply opens with the dock shown; nothing needs to read the old key. Signal identity
|
||||
is the *name* throughout (a CONFIG renumbers channels), 'enabled' is read off the channels the
|
||||
model built -- an enabled signal is exactly one that produced a channel -- and `apply_layout()`
|
||||
installs the graphs **before** assigning the rows, since a row may hold a math channel's name and
|
||||
|
||||
@@ -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.
|
||||
@@ -412,12 +412,19 @@ precedent, not an exception.
|
||||
pauses, reports again with `dn=` per measurement, then resets and reports a third time. Three
|
||||
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 on the
|
||||
Measure tab, a forced pass, then the tab switched away and read again two seconds later. **`advanced=4`
|
||||
is the assertion** (every accumulator gained passes while its page was off screen); `advanced=0` is the
|
||||
defect where the accumulation is gated on the visible tab, which would leave a gap in min/max/avg for
|
||||
- `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 to 3000 px so a scroll is
|
||||
observable on a short 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
|
||||
exactly as long as the trigger was on screen. It enables *and plots* its own signal, so it needs no other
|
||||
probe ahead of it. The reading's own entry is in `probe-readings.md`.
|
||||
probe ahead of it. The reading's own entry is in `probe-readings.md`. (Probe name `dock_section_probe`; the
|
||||
variable and the `[side-tab-probe]` tag keep their old names so existing scripts keep working.)
|
||||
- `UDPSCOPE_PROBE_DOCK_SESSION_MS=2500` with `UDPSCOPE_EXIT_AFTER_MS=8000` -- the session file's `dock` key,
|
||||
end to end: the dock hidden, saved and reloaded; shown again; and a file from the previous version
|
||||
(`side_tab = "measure"`, no `dock`) loaded without an error. Three `[dock-session]` lines, each `ok=1`;
|
||||
entries in `probe-readings.md`.
|
||||
- `UDPSCOPE_PROBE_SLIDEOVER_MS=1500` with `UDPSCOPE_EXIT_AFTER_MS=5000` (and `UDPSCOPE_ENABLE_ALL_MS=500`) -- the
|
||||
slide-over panel over the plot's right edge, opened on a throw-away `probe` page, read 0.5 s later. Two
|
||||
`[slideover]` lines. Line 1 must read `panel=360x<overlay height> ... edge_flush=1 full_height=1
|
||||
@@ -631,12 +638,12 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
bullet) — so a probe run against a fresh config directory will have it on screen for the first
|
||||
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 puts the sidebar on **Measure**,
|
||||
- `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 … side_tab=signals … contains=1` and
|
||||
`[toursurface] collapsed: side_stack_mapped=0 steps=2 signals_stop=0` are the two assertions; its
|
||||
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
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user