- 08-trigger.txt: names all four condition shapes and the buffer search; fixes the first paragraph and 'How to reach it' to match what the panel actually contains (fix 3). - 10-measurements.txt: adds a Statistics bullet — one observation per recompute, what resets the history, what does not (fix 3). - probe-readings.md: re-quotes condition-probe lines with level2= and mode= fields; corrects leg-2 window geometry (0.2 s / 10 % pre, not 1.0 s / 50 %); moves the wide-window reason to legs 1 and 3; fixes the search-probe scheduling note (search_probe calls condition_probe directly; do not also set CONDITION_MS) (fix 6). - verification.md: corrects shape-2 window geometry to match the code (fix 7). - invariants-session-and-export.md, README.md: document the measure_stats session key — position, type, default false (fix 8). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
Invariants: the session file, the export and the source chooser
What a session persists and in what order, when a save is refused, and when an export is collected.
Part of CLAUDE.md. These are rules that cost a defect to learn; read the ones touching the area you are editing before you edit it.
- The session is the layout.
LayoutStatepersists what makes a reopened scope look like the one that was closed: the window, the rows and their slot signals, the calibration, the enabled signals, the chosen trace colours ([[color]],name+rgb = "#RRGGBB"— only the overrides, so a stream's new signals still take the positional palette and a saved session does not freeze a colour that is meant to follow the signal list), the math channels with their graphs, the measurements pane, andmeasure_stats— the measurements statistics toggle; written as a bare top-level boolean (measure_stats = true/false), defaults tofalsewhen absent. Signal identity is the name throughout (a CONFIG renumbers channels), 'enabled' is read off the channels the model built -- an enabled signal is exactly one that produced a channel -- andapply_layout()installs the graphs before assigning the rows, since a row may hold a math channel's name and that name is only a signal once its graph is in. Two format rules, both learned the hard way: write an op or a measure type as its index throughmath_op_at()/measure_type_at(), never as the raw enum value -- those accessors' orders are the dialog's, not the declaration order, so writing one and reading the other silently renames operations. And a top-level key written after a[[table]]belongs to that table in TOML:enabled = [...]emitted after[[calibration]]was parsed into the last calibration record and the enable state was lost without a word.mkdirs()used to create only components with a trailing slash, so~/.config/udpscopewas never made and the first save failed silently -- worth knowing when a layout change "does not stick". - The export is collected at click time, not in the file-dialog callback. The live view keeps scrolling while the chooser is up, so a late collection would write a window the user never saw. The writer is picked by the chosen path's extension.
- The chooser runs before
ScopeModelexists, and--serveris what decides.Configcarriesserver_specified, set only by the--serverbranch, andmain()asks only when it is false — so declining costs no buffer and no socket, andScopeModel,Acquisition,RowSetandrun_ui()need no notion of "no source yet". It is--serveralone and not "no arguments":--multicaststill needs a server to fetch CONFIG from. Every documented probe passes--server, which is what makes the feature invisible to the regression net — a probe added without it would reach the chooser and hang.gtk_init()plus a nestedGMainLoop, and thenrun_ui()'sGtkApplicationafterwards in the same process, is a verified combination (gtk_init()is idempotent; running your own loop is the documented GTK4 replacement for the removedgtk_dialog_run()). - A session file's name cannot say how its source was reached, so the file says it.
<host>_<port>.tomlcarries neither the multicast group nor the interface, so a chooser row could not reconnect to a multicast source at all.LayoutState::multicast/interface_addrare written in the top block, before the first[[table]]— a bare key after a table header is that table's key in TOML, which is exactly howenabledwas parsed into the last[[calibration]]record and lost without a word. A file from an older build has neither key and loads as unicast on the default route. The chooser's list filters to sessions that name at least one signal, because a probe run leaves a file per throwaway port and all of them holdsignals = ["", "", "", ""];known_sources()splits the filename at the last_, sincesanitize()turns anything unsafe in a host into one. save_layout_now()refuses to write whilesignals()is empty. A run against a source whose streamer was down receives no CONFIG, so every slot name stays empty and the save would overwrite a configured session withsignals = ["", ...]. The chooser then filters that file out — a source whose streamer was merely down during one run would silently vanish from the only list it can be picked from, for ever. Keeping the old file loses nothing: a run that configured nothing had nothing worth persisting.