docs: source badge name only, no signals lock, the Add-a-signal page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-03 02:14:37 +01:00
1 parent b98a3478ff
commit d8f82bd610
8 files changed
+59 -49

No files matched your search

+1 -1
View File
@@ -123,7 +123,7 @@ overlay is built (the tour's overlay wraps the whole window, so its dim rectangl
too); the trigger form is registered as
a page by `build_trigger_panel`; `build_source_panels()` registers the source pages; and
`build_badge_bar()` runs *after* all of them, because the Source badge needs to know the first page.
Per-tick state (the Trigger word, the Source name and `connected`, the open marks) is written by
Per-tick state (the Trigger word, the Source name, the open marks) is written by
`sync_badge_bar()`, called from `tick()` and from `slideover_open`/`slideover_close`; it writes a label
only when its text changed. Nothing in the badge bar or the slide-over may be read from the producer
thread.
+2 -2
View File
@@ -255,8 +255,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
`gtk_box_reorder_child_after` and rewrites `App::badge_order` from the widgets, and **never rebuilds the badges** --
the dragged badge and the target whose `drop` emission is running must both survive it. A foreign drag whose string is not a
signal index is refused. `[sigbadges]` reads the controllers, the payload and the reorder; `pane drag:` reads the close.
- **A stock icon name has to be one *Adwaita* has, and a blank button is the failure mode.** The two
panes' `[edit]` buttons are `document-edit-symbolic` (the pencil: edit) and `object-select-symbolic`
- **A stock icon name has to be one *Adwaita* has, and a blank button is the failure mode.** The
trigger pane's `[edit]` button is `document-edit-symbolic` (the pencil: edit) and `object-select-symbolic`
(the check: done). The done one was `emblem-ok-symbolic`, which breeze ships and **Adwaita does not** —
so the button that leaves configuration mode was blank, which no other reading could see: the button
worked, the layout was unchanged, and only `pane edits:` (the two names and
+3 -3
View File
@@ -116,8 +116,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
- **The badge bar has a fixed height and sits outside the overlay.** `badge_bar` is 36 px
(`height-request`) between `plot_overlay` and the status line, so the plot above it never moves when a
badge's text changes (`bar_h` = `bar_h_after` in `[badges]`). This is "layout must not depend on
content" for the new row: a badge's state is a word from a short fixed set and the Source badge's name
is a kind's display name, so what changes with the stream is the row's *width* (the bar has room to
content" for the new row: the Trigger badge's state is a word from a short fixed set and the Source badge
is a kind's display name only (no connection state: that is the status bar's), so what changes with the stream is the row's *width* (the bar has room to
spare on the right) and never its height. The badge labels are not ellipsized: a name long enough to
need it would be a defect of the kind's `display_name()`. Rows that appear by trigger condition appear
inside the slide-over's scroller, never in the bar.
@@ -127,7 +127,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
(the E-1608: eight inputs and the counter) would push the window's minimum width past a laptop screen.
Past `kMaxSignalBadges` (6) the row is replaced by one "N signals" badge, which also stands for the `+`.
The bar's height stays 36 px in every one of these states (`bar_h_fixed` in `[sigbadges]`), and
`bar_min_w` reads the same with one signal, nine signals and the overflow (329 on the E-1608, 336 on UDPS:
`bar_min_w` reads the same with one signal, nine signals and the overflow (244 on the E-1608, 251 on UDPS:
the Source badge of each kind reads differently). A change that makes it grow with the signal count has
taken the scroller out of the measurement.
- **The dock's width is fixed too** (`side_column` `width-request` 300, the split view's 320 px floor
+3 -3
View File
@@ -13,9 +13,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
(`rebuild_trigger_signals()` picks up `App::pending_trigger_channel`, or `apply_trigger_layout()`
resolves it itself when they are already there) — a name that resolved to nothing would silently move
the trigger to channel 0.
- **A session that loaded is what locks the two panes.** Locked is the returning user's mode: the
trigger pane shows one compact line, the signal list only the signals that are on, and neither offers
a control that changes the configuration. A *first run* — no session file for that source — never
- **A session that loaded is what locks the trigger pane.** Locked is the returning user's mode: the
trigger pane shows one compact line and offers no control that changes the configuration (the signals
have no lock). A *first run* — no session file for that source — never
reaches `apply_layout()` and stays in configuration mode, which is what a first run is for. The mode
is a mode and not a setting: it is deliberately not persisted, so a scope reopened tomorrow starts
where a returning user expects to start. **Dragging a signal onto a plot is not configuration and survives
+33 -29
View File
@@ -1706,8 +1706,8 @@ library measures and draws the rect in tests of its own.
```
[udpscope] pane drag: slide-over closed for the drag=1
[udpscope] pane edits: locked=document-edit-symbolic(ok) config=object-select-symbolic(ok)
[udpscope] panes: trigger=locked signals=locked body_visible=0 summary='signal: square | condition: window, falling | level: 0.3 .. 0.6 | mode: single | noise band: 0.2 | pre-trigger: 20 % | hold-off: 1000 ms'
[udpscope] panes: trigger=config signals=config body_visible=1 summary=''
[udpscope] panes: trigger=locked body_visible=0 summary='signal: square | condition: window, falling | level: 0.3 .. 0.6 | mode: single | noise band: 0.2 | pre-trigger: 20 % | hold-off: 1000 ms'
[udpscope] panes: trigger=config body_visible=1 summary=''
[udpscope] panes: the same line as the probe found it: 'mode: stream (trigger off)'
```
@@ -1720,18 +1720,18 @@ or more than six, where the badges are replaced by the "N signals" overflow). It
`sidebar hidden for the drag=1 restored after it=1` pair, which tested `row_drag_sidebar()` -- the list rows were
the drag handle and the overlaid sidebar had to step aside; both are gone.
**`pane edits:`** is the two `[edit]` buttons in each mode, by name and verdict, and it exists because of
**`pane edits:`** is the trigger pane's `[edit]` button in each mode, by name and verdict, and it exists because of
a real defect: the "done" icon was `emblem-ok-symbolic`, which **Adwaita does not have** (breeze does), so
the button that leaves configuration mode was blank — and a blank icon button is invisible in every other
reading, exactly as `toolbar icons:` describes for the toolbar. The probe clicks into configuration mode
and back, so one line carries both names. **Both must read `(ok)`**, and the pair is
`document-edit-symbolic` (the pencil: edit) and `object-select-symbolic` (the check: done).
Both panes have two modes. **Locked** is what a returning user gets — the trigger pane shows the one line
in `summary`, the signal list shows only the signals that are on, and neither offers a control that
changes the configuration — and **config** is the full pane, which is what a *first run* gets, because a
session file that loaded is what "we have been here before" means. Each heading's `[edit]` button flips
its pane.
The trigger pane has two modes. **Locked** is what a returning user gets — the pane shows the one line
in `summary` and offers no control that changes the configuration — and **config** is the full pane, which
is what a *first run* gets, because a session file that loaded is what "we have been here before" means.
The heading's `[edit]` button flips it. (The signals have no lock: the badges and the signal pages are
direct manipulation, and `panes:` no longer carries a `signals=` field.)
- **With trigger mode off the summary is one line**: `mode: stream (trigger off)`. The four lines below
describe a trigger that is not running, so quoting them would describe nothing. The dump carries the line
@@ -1765,9 +1765,8 @@ its pane.
`body_visible=0` is a locked pane showing nothing at all, which is the defect this reads for.
- **`body_visible=0`** is the settings being off screen, which is what makes the pane a couple of hundred
pixels instead of 580 in `panels:`.
- **The locked signal list drops the rows of the signals that are off**, so `rows` counts what is on:
`signals panel: … rows=3 signals=2 divider=1` is two on signals and the `MATH` divider, not four
signals. Its `+` goes with them — a `+` on a locked list opens the editor the lock exists to keep shut.
- **The signals list has no lock.** It lists what can be *added* (see `signals panel:`), and the signal
pages always allow acquire, colour, calibrate, math edit and delete.
## `panel switches:` and the trigger panel's default sensitivity
@@ -2305,19 +2304,26 @@ prints without a probe: `depth`, the retention (`kept`/`bytes`/`budget`/`dropped
## `signals panel:`
```
[udpscope] signals panel: labels=2 list=1 rows=5 signals=4 math=0 divider=1
[udpscope] signals panel: labels=2 list=1 rows=9 signals=4 math=4 divider=1
[udpscope] signals panel: labels=3 list=1 rows=5 signals=4 math=0 divider=1 drag_sources=0 addable=4 addable_ok=1 empty_label=0
[udpscope] signals panel: labels=3 list=1 rows=2 signals=1 math=0 divider=1 drag_sources=0 addable=1 addable_ok=1 empty_label=0
[udpscope] signals panel: labels=3 list=1 rows=1 signals=0 math=0 divider=1 drag_sources=0 addable=0 addable_ok=1 empty_label=1
```
The signals panel's frame read from the window, and in the same line the *shape* of the one channel list:
the heading and the hint the `.ui` gives it, the list the rows go in, how many rows that list holds
(`rows`), how many of them are streamed signals (`signals`), how many math channels the app has (`math`),
and `divider`, which is 1 when `rows == signals + 1 + math` and 0 otherwise. **`drag_sources` must be
The signals panel's frame read from the window, and in the same line the *shape* of the one list. The page
is "Add a signal": it proposes only what can be added, so the list holds the streamed signals that are not
acquired (`signals`), the math channels that are not computed (`math`), and the `MATH` divider with its `+`
(`divider`, 1 when `rows == signals + 1 + math`, 0 otherwise). An acquired signal has a badge in the bar and
is not listed -- **unless the badge bar has overflowed** (more than 6 enabled signals, the "N signals" badge),
when the acquired ones are listed too, since the bar no longer shows them. `addable` is the same count
worked out from the model (unacquired listed signals, or all of them when overflowed) and `addable_ok=1` is
`signals + math == addable`; `0` is a list filter that drifted from the rule. `empty_label=1` is the dim
"every signal is acquired" label shown, which must happen exactly when `addable=0` and the stream declares
signals (the divider row stays, so a math channel can still be created). **`drag_sources` must be
`0`**: dragging a signal onto a plot is the signal *badge's* job now (see `[sigbadges]`, which counts the
badges' own sources), and the list's rows only open the signal's page; a non-zero reading is a second, competing
drag handle on the list. `labels=1` is a `.ui` that lost
one of the two labels — counted over the panel's subtree *above* the list, because the rows' own name
and unit labels are the code's and the heading sits in a heading row beside the pane's `[edit]` button — `list=0` one that lost the list -- both of which otherwise show as a panel quietly
drag handle on the list. `labels=2` is a `.ui` that lost
one of the three labels (heading, hint, the empty-state label) — counted over the panel's subtree *above* the list, because the rows' own name
and unit labels are the code's — `list=0` one that lost the list -- both of which otherwise show as a panel quietly
missing a piece -- and **`divider=0` is the merged list that lost its `+`**, the one control that can create
a math channel. The first line above is a run with no math channels at all: the divider is there anyway,
because a `+` that only appeared once a channel existed could never create the first one. `panels: …
@@ -2640,17 +2646,16 @@ left the caller's `false` alone would read `dock_shown=0 default=1 ok=0`. The pr
## `[signalpage]`
```
[signalpage] name=sine open=1 pages=1 enable_visible=1 plot_buttons=1 plot_on_row0=1 plot_on_row0_disabled=1 locked_enable_sensitive=0 removed=1 btn_row0=1 back_to_list=1 row_opens=1 pruned=1 other_page_kept=1 rows=1
[signalpage] name=sine open=1 pages=1 enable_visible=1 plot_buttons=1 plot_on_row0=1 plot_on_row0_disabled=1 removed=1 btn_row0=1 back_to_list=1 row_opens=1 pruned=1 other_page_kept=1 rows=1
```
`UDPSCOPE_PROBE_SIGNAL_PAGES_MS` builds and drives the page of the first streamed signal. `open=1` (the slide-over shows `signal:<name>`), `pages=1`
(one page in `App::signal_pages`), `enable_visible=1` (the acquire tick is shown in live mode; `0` in a file view), `plot_buttons=` equal to
`rows=` (one "Plot on" button per figure row), `plot_on_row0=1` (`assign_signal_to_row()` returned true and the signal is plotted, from a state where the probe first took it off every plot), `plot_on_row0_disabled=1`
(after the page refilled with the signal on plot 1, that plot's "Plot on" button is insensitive, "already on this plot"; `0` is a button that would put the signal in a second slot of
the same plot), `locked_enable_sensitive=0` (with `signals_locked` set and `signal_pages_sync()` run, the page's Acquire/Compute tick is insensitive; `1` is a page that bypasses
the signal lock -- the same goes for a math channel's Edit graph and Delete, which the probe does not read; the probe restores the lock), `removed=1`
(the page's "Remove from plots" took it off), `btn_row0=1` (the "Plot on 1" button, the user's path, put it back), `back_to_list=1` ("All signals"
target), `row_opens=1` (activating the list row opens the page), and `pruned=1`: a math channel's page was opened, its **Delete** button clicked, and
the same plot), `removed=1`
(the page's "Remove from plots" took it off), `btn_row0=1` (the "Plot on 1" button, the user's path, put it back), `back_to_list=1` ("Back to signals"
target), `row_opens=1` (the probe switches the signal off so it is addable, activates its list row and the page opens; the signal is then switched back on), and `pruned=1`: a math channel's page was opened, its **Delete** button clicked, and
the slide-over closed and the page was dropped by `signal_pages_sync()`. `pruned=-1` means no math channel could be added; `0` is a stale page. `other_page_kept=1`: with the `trigger` page open, a signal page that is not on show was pruned (a math channel added, its page built, the
channel removed) and the slide-over was still on `trigger`, both by `slideover_page()` and by the stack's visible child; `0` is `signal_pages_sync()` parking the stack on the
signals list while the title and the badge marker still name `trigger`; `-1` when there is no `trigger` page or no math channel could be added.
@@ -2658,14 +2663,14 @@ signals list while the title and the badge marker still name `trigger`; `-1` whe
## `[sigbadges]`
```
[sigbadges] enabled=4 badges=4 overflow=0 overflow_text=- plus_visible=1 dot_plotted=4 bar_h=36 bar_h_fixed=1 bar_min_w=336 opens=1 plus_opens=1
[sigbadges] enabled=4 badges=4 overflow=0 overflow_text=- plus_visible=1 dot_plotted=4 bar_h=36 bar_h_fixed=1 bar_min_w=251 opens=1 plus_opens=1
```
`UDPSCOPE_PROBE_SIGNAL_BADGES_MS` reads the signal badges. `badges` must equal `enabled` (the enabled, listed signals) while `enabled` is at most 6
(`kMaxSignalBadges`); beyond that `badges=0 overflow=1 overflow_text=<N> signals plus_visible=0` (the "N signals" badge replaces the badges and the "+"). `dot_plotted` is the number of
badges whose signal is on a plot (its dot is then the trace colour, not the unplotted grey). `bar_h` is the bar's allocated height and `bar_h_fixed=1` that neither it nor its natural
height moved while one signal was switched off and on again (`rebuild_signal_panels()` each time), and that the badge count followed; `0` is a bar whose height depends on its content.
`bar_min_w` is only a record: the scroller must keep it the same however many signals there are (it reads 336 with the UDPS mock's 4 and with the overflow forced on, and 329 against the E-1608 mock with one signal and with nine -- the overflow, `enabled=9 badges=0 overflow=1 overflow_text=9 signals plus_visible=0 bar_h=36 bar_h_fixed=1`; a value that grows with the signal count is a scroller that has stopped constraining the bar). `opens=1`: clicking the
`bar_min_w` is only a record: the scroller must keep it the same however many signals there are (it reads 251 with the UDPS mock's 4 and with the overflow forced on, and 244 against the E-1608 mock with one signal and with nine -- the overflow, `enabled=9 badges=0 overflow=1 overflow_text=9 signals plus_visible=0 bar_h=36 bar_h_fixed=1`; a value that grows with the signal count is a scroller that has stopped constraining the bar). `opens=1`: clicking the
first signal badge opens `signal:<name>` with the badge marked open and a second click closes it (`-1` when overflowed, there being no signal badges); `plus_opens=1` the same for the "+"
(the overflow badge when overflowed) and the `signals` page.
@@ -2697,8 +2702,7 @@ any is a badge that does not say its panel is open, or one that stays marked aft
**Line 2** is the Source badge, and every field has a value it must read:
- `name=` the source kind's name and `state=` `connected`/`disconnected`, as the badge shows them. `connected` against a live mock; a badge stuck on
`disconnected` is the status not reaching the badge.
- `name=` the source kind's name, as the badge shows it. The badge carries no link state (the status bar says it); there is no `state=` field.
- `pages=` the number of slide-over pages the source kind contributes (`source-<id>`): `0` for UDPS, `2` for the E-1608 (`source-ao`, `source-dio`).
`can_target=0` with `pages=0` is the UDPS badge being a read-out rather than a button; `can_target=1` with `pages>0` is the button being live.
`can_target=1` with `pages=0` is a dead button that opens nothing.
+4 -3
View File
@@ -472,7 +472,7 @@ precedent, not an exception.
a sensitivity, a visibility or a value needs no open page.
**The source kind's panels are slide-over pages too** (`source-<id>`: the E-1608's `source-ao` and
`source-dio`, Flat UDP's, the probe kind's), so the same rule holds for them. The probe appends a second
`[badges]` line for the Source badge: `[badges] source name=<kind> state=<connected|disconnected> pages=<n>
`[badges]` line for the Source badge: `[badges] source name=<kind> pages=<n>
switch_visible=<0|1> can_target=<0|1> second_page=<id|-> second_ok=<0|1> open_marked=<0|1|-1>
closed_unmarked=1`. `pages` is the number of source pages, `switch_visible` is the segmented switch
being on screen after the first source page was opened (`1` for the E-1608's two pages, `0` for one page
@@ -1035,8 +1035,9 @@ stages into one is the failure mode**: a probe that rebuilt the buffer at 11.5 s
immediately would read `bare=0` on every search leg, which looks exactly like a broken comparator.
**Why 4 s and not 3.** `enable_all_probe`'s last act is a timeout of its own, `ENABLE_ALL_MS + 2000` -- 3 s
under this recipe -- which walks `signal_list` for the front channel's acquire tick and clicks it. A probe
that installs a math graph at that same instant rebuilds the signal panel under it, and the off-step then
under this recipe -- which opens the front channel's signal page and clicks its "Acquire this signal" tick (an
acquired signal is not in the Add-a-signal list, so the list cannot be walked). A probe
that installs a math graph at that same instant rebuilds the signal panels under it, and the off-step then
emits `active` on a freed `GtkCheckButton`: `GLib-GObject-CRITICAL: instance with invalid (NULL) class
pointer`, with the backtrace in `enable_all_probe()::{lambda}`. It is a real fragility of that probe's lookup
and not of the feature, and it is why every documented probe time is offset from `ENABLE_ALL_MS + 2000`, not