Files
udpscope/CLAUDE.md
T
Martino Ferrari c1460a6977 feat: the manual is a Mallard set opened in Yelp, not a GResource in the binary
The help was 14 Pango-markup `.txt` bodies in a second GResource, drawn into a paged `GtkWindow`
built in `help_window.cpp`. That made F1 a viewer of ours with no search index, no cross-references
and no translation, and it kept the manual in the *binary* when every other desktop application's is
a document on disk. It is now a real Mallard set:

  help/C/org.udpscope.app/  index.page + 14 topic pages + figures/*.png

and `show_help()` hands `help:org.udpscope.app` to whatever the desktop registered for the `help:`
scheme -- Yelp on GNOME -- through a `GtkUriLauncher`, reporting a launch failure with a
`GtkAlertDialog` rather than a click that silently does nothing. The paged window's builder, the
page table and `help_self_check()`'s markup validation are gone with the resource.

The document identifier is the desktop file's own basename (its desktop-file id), derived in
CMakeLists from `data/org.udpscope.app.desktop.in`, so the same string is the `.desktop` name, the
GApplication id and the install directory at once. The set is installed by a new `install()` rule to
`<datadir>/help/C/org.udpscope.app/` -- where the freedesktop Help System Specification's `help:`
URI resolves `help:<id>` -- so the manual is read from a datadir by another process even though the
binary has no install step. `UDPSCOPE_HELP_NAME` and `UDPSCOPE_HELP_DIR` are compiled in from that
one place.

The 14 pages were migrated fact for fact, but restructured the Mallard way: a `<desc>` per page
(what a search result shows), `<steps>` for procedures, `<terms>` for option lists,
`<gui>`/`<key>`/`<keyseq>` for interface words and keys, `<note>` for the asides, and a topic graph
via `<link type="guide" xref="index#main"/>`. The figures are annotated screenshots produced by
`tools/make-help-shots.sh`, which drives the app headlessly against `tools/mock_udpstreamer.py` and
draws the boxes with ImageMagick; the side panel's box is measured from the app's own `side=NNNx`
dump reading, the header and status bands come from a documented table, and a crop whose standard
deviation is 0 fails the script.

`cmake --build build --target test-help` checks the set the way `test-ui` checks the `.ui` files:
`yelp-check`'s offline subcommands (links, media, orphans, ids) and a `yelp-build html` render.
`yelp-check validate` is deliberately absent -- it fetches the Mallard RelaxNG schema over the
network and fails offline for that reason, not for a fault in the pages.

The `[help]` probe reading changes with the resource it was written for. `parsed == pages` over
in-binary markup cannot exist now, so `help_self_check()` scans `UDPSCOPE_HELP_DIR` and the line
reads `topics=14 index=1 figures=3`; `topics=0 index=0` is the manual missing from the tree, which
is the finding the old count named.

Verified: clean build with the documented three warnings (the help bundle's `-Woverlength-strings`
is gone, so the baseline moves from four to three); test-ui 14/14; test-help clean (the four
subcommands and 15 pages rendered); a temp-prefix install lands 15 `.page` files and 3 figures at
`share/help/C/org.udpscope.app/`; the headless run reads `[help] topics=14 index=1 figures=3 dir=...`
with 0 Gtk-CRITICAL. Yelp's own window could not be exercised on this box -- it starts under Xvfb,
activates its portals and never maps, the same WebKit/portal limitation that stops a grab -- so its
rendering and its search index are unverified here; the content pipeline was (`yelp-build` renders
the whole set).

Docs: the manual's build/install/open story in `build-and-run.md` and README; the warning baseline
in `verification.md`/CLAUDE.md; the help bullets in `invariants-gtk-and-chrome.md`; the `[help]`
line in `probe-readings.md`.
2026-10-14 16:49:30 +02:00

5.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this project is

udpscope is a GTK4 oscilloscope-style viewer for a UDPStreamer source. It receives one UDP stream with deps/udpsclient, buffers it in a large ring (default 200 MB), and draws it with deps/plotter — a figure of up to eight stacked rows sharing one x window. On top of the live view it provides the things a bench scope provides: a trigger with a pre/post event window, a pause that freezes the visible window, cursors, statistical measurements, user-defined math channels built in a node editor, and an export of the visible window to .npy/.csv.

Two documents bound the work and are not interchangeable with this one:

  • SPECS.md is the requirement document — what was asked for.
  • README.md documents what was actually built, including deliberate deviations from the spec and known limitations. Read it before concluding something is a bug.

Three things shape almost every change here, and the linked files expand on each:

  • There are exactly two threads — a producer that owns the UDP client and the GTK main thread that owns every widget and the GL context — and they share ScopeModel under one mutex. Samples cross through lock-free rings, never through the lock.
  • There is no unit test suite. The only automated check is the application's own headless self-check, driven by environment variables under xvfb-run against a mock source. Extend the probe/dump in src/ui.cpp rather than adding a separate harness.
  • Every source file carries an SPDX MIT header; match it in new files.

Table of contents

The detail lives in docs/claude/. Read the files that touch the area you are editing before you edit it — most of their contents are rules that cost a defect to learn.

Working in the repository

File What it covers
build-and-run.md Configuring and building, the desktop-entry target, the command-line options, and the two vendored dependencies (deps/plotter, deps/udpsclient) including what was changed upstream and why.
verification.md How to run the headless self-check, the two baselines every run is read against (exactly three build warnings; no Gtk-CRITICAL), and the environment variable that schedules each probe.
probe-readings.md The per-line reference for the self-check's output: what every probe and dump line means, the value it must read, and the defect a wrong reading names.

How the application is put together

File What it covers
architecture.md The two threads and what they share; the one BufferStore and the four display modes (Live, Capturing, Held, Waiting); the display clock and stream_at_x0(); the layers to keep straight (SignalInfo → ChannelBuffer → SlotSeries → RowSet).

Invariants worth knowing before editing

File What it covers
invariants-plot-and-layout.md What plot::Figure/plot::Plot can and cannot be asked to do, what a row's contents belong to, the layout editor's overlay children, and the rule that layout must not depend on content.
invariants-readouts-and-cursors.md A readout quotes a stored sample and never a value between two; how the cursors are read back from the plots; the one formatter both panes share.
invariants-data-path.md What rebuilds the buffer and drops history, why decimation at ingest is an average and never a stride, and the two rules the f64 time ring and its cached first stamp impose.
invariants-rendering.md How the renderer maps a visible range onto samples (never the dx() grid), what gather() emits and relative to which origin, why every series needs a real MinMaxPyramid, and why a dense trace is drawn one pixel column at a time.
invariants-trigger-and-pinning.md The event window counted in stream seconds end to end, what pins an axis in each mode, and what each pin is measured from (the snapshot's own stamps, not the request).
invariants-time-axis-and-window.md No plot interaction changes the time scale; apply_time_window() is the only path that does; how the x labels pick their form; what a fit restores.
invariants-measurements.md Why the pane is refreshed on a cadence, why neither pass may read its window raw, and how a crossing is counted with hysteresis.
invariants-math-channels.md A math channel is a channel; the engine runs on the producer thread, so a regression there costs packets and not frames; the node editor's own rules.
invariants-session-and-export.md What a session file persists and in what order, when a save is refused, and when an export is collected.
invariants-gtk-and-chrome.md The GTK signal-signature and dialog-lifetime traps this app has already fallen into, the status bar's two tiers, the six bundled icons and their authoring rules, and how the Mallard manual is opened.