The spec claimed the math pane and the signal list were two lists of the same
channels; they are one. `append_math_signals_locked()` already puts every math
channel into `signals()` and nothing filters `sig.math`, so the merge is mostly
a deletion -- the list gains a `MATH` divider and a second menu, and the pane
goes. §2, §3 and §6 say so now.
The plan is seven tasks, each ending in a build, `make test-ui` and one short
headless probe read.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Five revealers in one column leave every section short of room once the
trigger, cursors, measurements and math panes are open. The design splits
the column into two tabs, folds the math list into the signal list -- the
two were the same list drawn twice -- and makes a feature's toggle disable
its pane rather than hide it, because a tab cannot carry an on/off.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EM interference is impulsive, so each place gets an amplitude qualifier and a
frequency one, both optional and both defaulting to today's behaviour: the
documented reading set has to come out unchanged. Records why the trigger's
zero-band path stays bit-for-bit, why a measurement filter gives up the bucket
fast paths, and why the new op is appended rather than placed beside the filters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two complaints, not one: the glitch is a row dropped before any style
knob is consulted, and the thinness is a choice udpscope made and never
revisited -- it asks deps/plotter for a trace thinner than the library's
own default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The two were asked for together and are one design: the packet counter is what
makes a hole addressable, so relative timing is the coordinate system in which
resequencing is arithmetic rather than inference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
format_measure_value() does not exist; the shared SI-prefix formatter is
format_measure(double, const std::string&) at src/ui.cpp:1004.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec described the new condition fields as appended session keys. LayoutState
carries no trigger field at all -- the trigger is built from --holdoff and the
panel's defaults on every launch -- so there was nothing to append to. Persisting it
is a separate feature nobody asked for, and a session that silently restored an armed
pulse-width condition would surprise rather than help. The statistics toggle stays as
the one session key this design adds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec claimed a window condition with equal levels degrades to an edge trigger on
that level. It does not: the band test is a change of side, so equal levels make the
signal "inside" only on exact float equality and the condition silently never fires.
Special-casing the detector into an edge trigger would conflate two state machines
that answer different questions -- the divergence the shared evaluator exists to
prevent. The panel refuses the degenerate band instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Qualified trigger conditions (pulse width with glitch as a comparison, window,
dropout), per-measurement statistics with a reset, and a search over the retained
ring. The first and third are the same question asked of live and of recorded data,
so they share one allocation-free EventDetector extracted out of TriggerEngine --
and the probe that proves the two paths agree on t0 is part of the scope.
Reference waveforms, PNG export, FFT and a byte-bounded event history are out, each
for a stated reason.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reported "cannot add edges" has a specific cause: the output port's hit
radius reaches past the box it belongs to, and the press handler re-derives the
owning node with a box test, so clicking the visible dot's outer half sets
pending_out to -1 and silently disarms. Edge deletion has no code path at all.
The design replaces the two-click protocol with a drag gesture, makes the list
order the editor's own invariant via a topological re-sort rather than a rule the
user must obey, adds a view transform with every hit test routed through its
inverse, and trades the modal alert on Apply for a live validation line. It also
records that the Cancel button still calls gtk_window_destroy -- the dangling
dialog pointer CLAUDE.md documents as fixed was only fixed on the Apply path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec asserted that a paintable's intrinsic size reads 0x0 for an SVG
librsvg refuses. It does not: gtk_icon_theme_lookup_icon() never returns NULL
and always reports the requested size, and a valid icon, a malformed one and an
absent name all snapshot to the same texture node. GMarkup accepts the double
hyphen, and gdk-pixbuf goes through glycin here and refuses our own icons.
So the malformed file is caught by preprocess="xml-stripblanks", which runs
xmllint during glib-compile-resources and fails the build with file, line and
column. The runtime line keeps its format but asserts only what it can: the
name resolves, the resource is present, and the root element declares the size.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Records the decisions taken before design — the application ID stays
org.udpscope.app, the icon is the instrument face, and the desktop entry is an
opt-in CMake target rather than an install() rule — along with the HIG
constraints the artwork is built to and the two silent failures the new
verification line has to tell apart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four features the user asked for together: an About dialog (version, build
timestamp, settings path), a shortcut overview covering the keyboard and the
mouse, a fourteen-page help window, and a status bar split into a short glance
line plus a details popover so the plot's own row stops being crowded.
Records one deviation from the approved design: keyboard zoom-x/zoom-y use
ctrl/alt rather than shift, because `+` *is* shift+`=` on the common layouts and
a bare zoom-in therefore already arrives with Shift set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Launched with no --server, udpscope silently connects to 127.0.0.1:8000 and
offers no way to pick a source or see which ones it already knows. The design
is a pre-flight dialog: it runs before ScopeModel exists, so a cancel costs no
buffer and no socket and the existing pipeline is untouched.
The list is built from the stored session files, filtered to those naming at
least one signal -- a development machine accumulates one file per probe run on
a throwaway port, 140 of them here -- and the session format gains multicast and
interface keys so a row can reconnect exactly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>