From d8f82bd6108f09774268faaf925e0e51f9c9f6ad Mon Sep 17 00:00:00 2001
From: Martino Ferrari
Date: Tue, 3 Nov 2026 02:14:37 +0100
Subject: [PATCH] docs: source badge name only, no signals lock, the
Add-a-signal page
Co-Authored-By: Claude Opus 4.6
---
README.md | 19 +++---
docs/claude/architecture.md | 2 +-
docs/claude/invariants-gtk-and-chrome.md | 4 +-
docs/claude/invariants-plot-and-layout.md | 6 +-
docs/claude/invariants-session-and-export.md | 6 +-
docs/claude/probe-readings.md | 62 +++++++++++---------
docs/claude/verification.md | 7 ++-
help/C/org.udpscope.app/overview.page | 2 +-
8 files changed, 59 insertions(+), 49 deletions(-)
diff --git a/README.md b/README.md
index 8f3f433..cba3458 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,13 @@ src/ui_signal_page.cpp the per-signal slide-over pages (`signal:`)
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
part is the **signals**: one badge per *enabled* signal (a dot in the signal's colour, filled while the
-signal is plotted, and its name), then a `+` badge that opens the full signal list (`g` does the same).
+signal is plotted, and its name), then a `+` badge that opens the **Add a signal** page (`g` does the same). That page proposes only what can be
+*added*: the streamed signals that are not acquired and the math channels that are not computed, each row with
+the tick that acquires it (the row then leaves the page and the signal's badge appears), plus the `MATH`
+divider's `+` for a new math channel; when every signal is acquired a dim "every signal is acquired" line
+stands in for the list. While the bar has overflowed into the "N signals" badge the page lists the acquired
+signals too, since their rows are then the only way to each signal's page. There is no signal lock: a
+signal's page and the list always allow acquire, colour, calibration, the math editor and delete.
A signal badge opens that signal's own page in the slide-over -- colour, calibration and the *Plot on N*
buttons -- and is also the drag handle: drag it onto a plot to draw the signal there, or onto another
badge to reorder them. Past **six** signal badges the bar shows a single **"N signals"** badge in their
@@ -112,7 +118,7 @@ place (it opens the list, and `+` is hidden): the E-1608's 8 analog inputs and c
otherwise. The signal badges sit in a scroller, so the bar's minimum width, and with it the window's,
never depends on how many signals there are. The badge order is kept **for the session only**; it is not
written to the session file. The right-hand end carries two words-only badges: **Trigger** (`off`, `armed`, `capturing`, `triggered`
-or `disarmed`) and **Source** (the kind's name and `connected`/`disconnected`; a read-out, not a button,
+or `disarmed`) and **Source** (the kind's name only -- the link state is the status bar's; a read-out, not a button,
for a source with no controls of its own, and for a file). A badge opens the **slide-over**: a 360 px
panel laid over the plot's right edge, opaque, with its own title, a *Close* button and one scroller per
page. The Trigger badge opens the trigger form; the Source badge opens the source kind's own panels
@@ -2937,11 +2943,10 @@ now — they are listed so they are not mistaken for anything else:
tabs did not fit 320 px. It is **one dock** now, with no tab switcher: the cursors and the
measurements in one scroller, shown from the start and insensitive until the stream has signals; `m`
scrolls it to the cursors; the **signal list** is the `signals` slide-over page (`g`, or the `+` badge). The **trigger form** and a **source's own
- controls** are pages of the slide-over, reached from the badge bar. Both panes also have a **locked** mode, which is what a returning user gets: the
- trigger pane shows one line saying what it is set to, the signal list shows only the signals that are
- on, and neither offers a control that changes the configuration -- each heading has an `[edit]` button
- to switch to the full pane, and back. Dragging a signal's badge onto a plot still works while locked --
- filling plots is not configuration. A signal's badge is dragged onto a plot, or onto another badge to
+ controls** are pages of the slide-over, reached from the badge bar. The trigger pane also has a **locked** mode, which is what a returning user gets: it
+ shows one line saying what it is set to and offers no control that changes the configuration -- its
+ heading has an `[edit]` button to switch to the full pane, and back. The signals have no lock: the
+ signals page only proposes what can be added, and the signal pages always allow editing. A signal's badge is dragged onto a plot, or onto another badge to
reorder the badges (the order is kept for the session only); the slide-over closes for the length of the
drag so there is something under the pointer to drop onto. A **first run** (no session file for that source) comes up
unlocked, because configuring the host is what a first run is for. Each pane's heading also carries its **own switch**, the same control as the
diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md
index 10319af..7446c30 100644
--- a/docs/claude/architecture.md
+++ b/docs/claude/architecture.md
@@ -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.
diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md
index deb5551..2fa3653 100644
--- a/docs/claude/invariants-gtk-and-chrome.md
+++ b/docs/claude/invariants-gtk-and-chrome.md
@@ -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
diff --git a/docs/claude/invariants-plot-and-layout.md b/docs/claude/invariants-plot-and-layout.md
index 52cbe9b..1b962d5 100644
--- a/docs/claude/invariants-plot-and-layout.md
+++ b/docs/claude/invariants-plot-and-layout.md
@@ -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
diff --git a/docs/claude/invariants-session-and-export.md b/docs/claude/invariants-session-and-export.md
index 99a2eec..23a3727 100644
--- a/docs/claude/invariants-session-and-export.md
+++ b/docs/claude/invariants-session-and-export.md
@@ -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
diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md
index 143253c..717ee86 100644
--- a/docs/claude/probe-readings.md
+++ b/docs/claude/probe-readings.md
@@ -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:`), `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= 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:` 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-`): `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.
diff --git a/docs/claude/verification.md b/docs/claude/verification.md
index 7c749b1..25f5d9f 100644
--- a/docs/claude/verification.md
+++ b/docs/claude/verification.md
@@ -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-`: 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= state= pages=
+ `[badges]` line for the Source badge: `[badges] source name= pages=
switch_visible=<0|1> can_target=<0|1> second_page= 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
diff --git a/help/C/org.udpscope.app/overview.page b/help/C/org.udpscope.app/overview.page
index fd605c2..9f95d4a 100644
--- a/help/C/org.udpscope.app/overview.page
+++ b/help/C/org.udpscope.app/overview.page
@@ -127,7 +127,7 @@
and port.
-
-
Enable a signal in the signal list. The plot stays empty for the first
+
Enable a signal in the Add a signal page (the + badge). The plot stays empty for the first
fraction of a second while the first window fills — the buffer holds only what has
arrived.