scope: full-screen figures, second windows, gated measurements, per-configuration events

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).
This commit is contained in:
Martino Ferrari committed 2026-11-05 17:58:59 +01:00
1 parent bfe13630f9
commit bdf0ff7ddc
39 files changed
+2267 -136

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
# AGENT.md
Working notes for an AI agent (or a new contributor) in this repository. **Read
[CLAUDE.md](CLAUDE.md) first, then the `docs/claude/` file that covers the area you are touching** —
this page is the fast path into it, not a replacement. `SPECS.md` is what was asked for; `README.md`
is what was built, deviations and limitations included; when the two disagree, README explains why.
## Build
```sh
git submodule update --init --recursive # deps/plotter is a submodule
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build # the app
cmake --build build --target test-ui # round-trips every .ui through blueprint-compiler
cmake --build build --target test-help # yelp-check over help/C/org.udpscope.app/
./build/udpscope --server 127.0.0.1 --port 8000
```
A clean build emits **exactly one warning** (`-Woverlength-strings` on the generated GResource
`.c`). Two is a regression.
## Verify
There is **no unit test suite**. The check is the app's own headless self-check against a mock
source; add a probe rather than a harness:
```sh
python3 tools/mock_udpstreamer.py --port 8000 --duration 120 &
xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=16000 UDPSCOPE_ENABLE_ALL_MS=1000 \
UDPSCOPE_PROBE_<NAME>_MS=3000 ./build/udpscope --server 127.0.0.1 --port 8000
```
Two baselines every run is read against: **`gtk warnings: 0`** in the exit dump (a `Gtk-CRITICAL` is
a defect, not noise) and the single build warning above. The probes, their env vars and the value
each line must read are in [docs/claude/probe-readings.md](docs/claude/probe-readings.md); how a run
is compared with the previous one is in [docs/claude/verification.md](docs/claude/verification.md).
For UI work, drive the real surface: the app under `xvfb-run` plus `xdotool`/`import` for pointer and
pixel checks (see `tmp/`-free examples in the probe sources), not only internal state reads.
## Hard rules
- **SPDX MIT header on every source file**, in the format the existing files use.
- **Two threads only**: a producer owning the data source and the GTK main thread owning every
widget and the GL context. They share `ScopeModel` under **one mutex**; samples cross through
lock-free rings, never through the lock.
- **A view never acquires.** Scope windows, full-screen and the layout editor are arrangements of
views over the one acquisition; they must not plan buffers, epochs or sources of their own.
- **Layout must not depend on content** (docs/claude/invariants-plot-and-layout.md).
- **Documentation moves with the code.** A behaviour change updates the matching
`docs/claude/invariants-*.md`, `README.md`, the Mallard page under `help/C/org.udpscope.app/`, and
the probe reading in `docs/claude/probe-readings.md` — the probe baselines are read as tests.
- **`deps/plotter` is a submodule and has its own history**: commit there first, then commit the pin
in this repository. Never edit its files without committing them both sides.
- No new dependencies, no generated-file edits, no silent changes to `SPECS.md` semantics: a
deliberate deviation is written into `README.md` under "Deviations from the spec".
## Commits
Subject: `<area>: <what changed>`, lowercase, imperative, no trailing period (`layout editor: …`,
`docs: …`, `plot: …`). The body explains *why* (the defect it fixes, the trade-off it takes), not a
restatement of the diff. Keep each commit one coherent change; probes, docs and the code they
describe travel together.