# 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 data source -- a UDPStreamer stream through `deps/udpsclient` by default, or another registered source kind (see `docs/claude/architecture.md`) -- 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 data source 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](docs/claude/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](docs/claude/verification.md) | How to run the headless self-check, the **two baselines every run is read against** (exactly one build warning; no `Gtk-CRITICAL`), and the environment variable that schedules each probe. | | [probe-readings.md](docs/claude/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](docs/claude/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`); the source plugins. | ### Invariants worth knowing before editing | File | What it covers | | --- | --- | | [invariants-plot-and-layout.md](docs/claude/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](docs/claude/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](docs/claude/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](docs/claude/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](docs/claude/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](docs/claude/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](docs/claude/invariants-measurements.md) | Why the pane is refreshed on a cadence, why neither pass may read its window raw, how a crossing is counted with hysteresis, and which mean over a split is which. | | [invariants-math-channels.md](docs/claude/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](docs/claude/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](docs/claude/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 bundled icons and their authoring rules, and how the Mallard manual is opened. |