A batch of view and instrument work, with the probes and docs for each.
- **Full screen per figure row**: a toggle on each row's top-right corner, revealed by
pointer proximity (`kMaximizeRevealPx` around its placed rect, per row, read from an
overlay-level motion controller so a revealed button under the pointer cannot flicker);
the plot menu offers the same as *Full screen this figure* / *Restore the layout*. The
arrangement is saved and restored through `RowState`, so nothing plotted moves. A layout
of one figure carries no toggle.
- **Second window**: `open_scope_window()` copies a figure (cells, width shares, signals in
their colours) or opens an empty one, with its own `RowSet` and `PlotArea`; the main
window's tick binds, acquires and repaints it, mirroring the time window and pin. Entries:
the plot menu's *Open this plot in a new window* and the overflow menu's *New scope window*.
- **Layout editor from the plot**: the plot menu gains *Edit the layout...* / *Done editing
the layout*, flipping the app's one toggle.
- **Measurements gate**: `MeasureOptions::gate` restricts a measurement to the held/pause
snapshot (live falls back to the live ring), persisted with the session and shown in the
measure dialog.
- **Crosshair axis badges**: a preference (`crosshair_axis_labels`, on by default) and the
plotter's badges where the lines meet the axes, in the axes' own formats; the plot menu
and the probe read them.
- **Event cache per configuration**: `EventStore` is keyed `<source>/<configuration>/`,
`retarget_event_store()` follows every configuration switch, and a pre-configuration cache
(loose files) is adopted into the configuration's folder on first start.
- **GTK4 lifecycle**: the main window's handlers move from the non-existent `"destroy"`
signal to `close-request`; scope windows are closed with it and the application quits
explicitly (the exit run's hang). The exit probe closes with `gtk_window_close()`.
- Probes: `[maximize-probe]` (single-figure rule and the corner reveal), `[scope-probe]`,
`[split-probe]` cell crosshair leg, `[prefs-probe]` store keying, `[configs]` store key,
`[rowmenu]` layout-editor entry; docs, help pages, README and AGENT.md updated.
Also fixes a probe-only use-after-free: `toggle_maximize_figure_row` replaces the figure
object, so the area must be re-pointed (the button's path does; the probe's raw call did not).
`UDPSCOPE_PROBE_SIDE_TAB_MS` is the one reading for the design's §5 rule -- the statistics
accumulator keeps running on the hidden page -- which nothing tested before. It switches to
Measure, forces one pass so there is a baseline, switches away to Signals and reads again two
seconds later: `advanced=4` is the assertion, and `advanced=0` is the defect where the
accumulation is gated on the visible tab, which would leave a gap in min/max/avg for exactly as
long as the trigger was on screen.
Two things the probe has to do for itself, and getting either wrong makes `advanced=0` a lie
about the tab:
- it **enables its own signal**, because a signal starts disabled (enabling is what starts
acquisition) and a probe that waited for one to be on would be reading another probe's side
effects -- and `UDPSCOPE_ENABLE_ALL_MS` is not part of this run;
- it **assigns that channel to a plot**, because a channel with no trace leaves the live window
*still*, and a still window is precisely the state `update_measures()` is supposed to skip.
Measured the hard way: without the assignment the probe read `advanced=0 min_n=0` with the view
pinned and only four passes in twelve seconds, none of them the tab's doing.
Docs: `probe-readings.md` gains the probe's entry and the signals panel's new shape
(`rows == signals + 1 + math`, `divider=0` a list that lost its `+`), loses the whole `math
panel:` entry, and follows `pane_sensitive=`, the gone `revealed=` field, the six toolbar
controls and the tab-reading `m`/`g` key pairs. `verification.md` gains the probe's bullet and
the re-measured warning figures. The invariants record the sidebar as a switcher above a stack
with a scroller per page, the hidden-page trap that replaces the unrevealed-revealer one for it,
that a feature's on/off is sensitivity rather than visibility, that a math channel is a row of
the signal list, and that a cursors-off readout blanks to `--` with the panel left on screen.
`shortcuts.ui` and the help page's window and sidebar bullets stop describing five revealers.
The README records the deviation from `SPECS.md` that names the panes as separate collapsible
sections.
Verified: `[side-tab-probe] page=signals rows=4 accums=4 advanced=4 min_n=16`;
`signals panel: labels=2 list=1 rows=9 signals=4 math=4 divider=1`; `panels: … (page_current=1)
(sensitive=0) switcher=1`; `toolbar icons:` six controls with every name `(ok)`, with the
Preferences dialog opened in the same run; zero `Gtk-CRITICAL` and no missing icon.
README gains the three controls where each lives -- the trigger's `band`/`reject`, the
measurement's `noise`/`hf`, and the math channel's `de-noise` node -- each with its unit, what
"off" is, and what it costs, plus the two deliberate deviations from the design spec next to the
features they belong to: the band applies to `edge` and `window` only (a pulse's width is measured
from a crossing to its return, so a band there changes the measured width rather than rejecting
noise; a dropout is an absence with no level to compare), and the band is verified through
`BufferSearch` hit counts rather than `[condition-probe]` latch times, because probe-readings.md
states that a `t0` is not an assertion.
The invariant docs gain the rules a later change must not break: `EventCondition`'s two new fields
and the once-per-packet `configure(dt)` that must not reset; the crossing rule that a change is
between two *decided* sides; the measurement path's exception to "a readout quotes a stored
sample", why the band moves the edge decision only, why `measure_accumulate` loops samples outer,
and why a filtered window disables both bucket fast paths; that `MathOp` is append-only (the index
*is* the serialisation), that the median window is pooled in the runtime rather than held per node,
why the pole reuses `b0`/`z1`, and that median-then-pole is the design; the session's named
`noise`/`hf` keys and its thirteen-field node string that still reads twelve; and the two GTK
traps -- positionally brace-initialised dialogs, and a control that cannot apply being hidden
rather than disabled.
The file was 1967 lines and is auto-loaded into every context, so every
session paid for the whole of it to reach one invariant. It now carries the
project's goal, the three facts that are always relevant, and a table of
contents; the body is fourteen files under docs/claude/ reached by link
rather than by import, so nothing is loaded until it is asked for.
The content is moved verbatim -- the only edits are each file's own header
and the cross-references that became cross-file ("see the invariant below"
is now a link to the file that owns it).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>