Files
udpscope/docs/claude/invariants-session-and-export.md
T
Martino FerrariandClaude Opus 4.6 728e7fbda5 docs: help pages, probe reference and session docs updated for the three new features
- 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>
2026-10-08 11:19:19 +02:00

55 lines
4.6 KiB
Markdown

# 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](../../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.** `LayoutState` persists 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**, and **`measure_stats`** — the measurements statistics
toggle; written as a bare top-level boolean (`measure_stats = true/false`), defaults to `false`
when 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 -- and `apply_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 through `math_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/udpscope` was 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 `ScopeModel` exists, and `--server` is what decides.** `Config` carries
`server_specified`, set only by the `--server` branch, and `main()` asks only when it is false —
so declining costs no buffer and no socket, and `ScopeModel`, `Acquisition`, `RowSet` and
`run_ui()` need no notion of "no source yet". It is `--server` alone and not "no arguments":
`--multicast` still 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 nested `GMainLoop`, and then
`run_ui()`'s `GtkApplication` afterwards in the same process, is a verified combination
(`gtk_init()` is idempotent; running your own loop is the documented GTK4 replacement for the
removed `gtk_dialog_run()`).
- **A session file's name cannot say how its source was reached, so the file says it.**
`<host>_<port>.toml` carries neither the multicast group nor the interface, so a chooser row
could not reconnect to a multicast source at all. `LayoutState::multicast`/`interface_addr` are
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 how `enabled` was 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 hold
`signals = ["", "", "", ""]`; `known_sources()` splits the filename at the **last** `_`, since
`sanitize()` turns anything unsafe in a host into one.
- **`save_layout_now()` refuses to write while `signals()` 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 with `signals = ["", ...]`. 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.