diff --git a/README.md b/README.md index f181642..2eaf7ab 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,21 @@ panel laid over the plot's right edge, opaque, with its own title, a *Close* but page. The Trigger badge opens the trigger form; the Source badge (when shown) opens the source kind's own panels (the E-1608's *Analog out* and *Digital I/O*, behind a segmented switch at the top of the panel). It closes with a second click on its badge, with `Esc`, with *Close*, or with a click on the plot; it never -resizes the plot. The right-hand **dock** (the existing sidebar, 320 px) holds what is left of the old -tabs in one scroller: the cursors and the measurements, shown from the start and insensitive until -the stream has signals (the signal list moved to the `signals` slide-over page). It is temporary -- see -"Review findings not (yet) implemented" for what the redesign still has to do with it. +resizes the plot. The right-hand **dock** (the sidebar, **280 px**, fixed) holds the **readouts**: a +compact, read-only copy of the cursors table (a header row `A B delta`, then one line per cursor +line) and of the measurements (one line each: the signal's colour chip, the type, the value). It is +shown from the start and insensitive until the stream has signals. Each of the two blocks has an +**Edit** button, which opens the full panel as a slide-over page -- `cursors` or `measure` -- over the +plot; `m` toggles the `measure` page, and a click on a measurement's line in the dock opens that +measurement's dialog. The header's **Cursors** toggle only switches the cursors on and off (it +mirrors the switch inside the `cursors` page); with the cursors off the dock's cursor lines are +insensitive, not hidden. Both pages are insensitive until the stream has signals, like the dock. The dock's +cells are a fixed number of characters wide and ellipsized, so the dock's width and height do not +depend on what a value reads: the **who** cell of a cursor line is 5 characters (the full text is its +tooltip), a value cell 9 (its tooltip is the full text). The dock and the pages show **one string**: +`update_cursors_pane()` and `update_measures()` format each value once and hand that variable to the +page's label and to the dock's, so the two cannot disagree. The remaining part of the redesign is +listed under "Review findings not (yet) implemented". Threading: one producer thread owns the data source (the UDPStreamer client is the built-in one) and calls `poll()`; the GTK main thread owns every widget and the GL contexts. @@ -1229,7 +1240,11 @@ decides which steps exist at all: a stop on a slide-over page is always kept, be reveals it, while one whose *pane is collapsed* (the dock, in a window too narrow to put it beside the plot) is dropped, because it is unmapped with its pane (a hidden page keeps its own *visible* flag true -- the widget's own flag, which is all `is_visible()` walks -- so the old test walked over -surfaces that were not on screen); a narrow window therefore walks four steps. The chooser's +surfaces that were not on screen); a narrow window therefore walks five steps. The dock's stop +describes the readouts and the **Edit** buttons; it names no slide-over page, so it closes the panel +like the plot's. The cursors and measurements pages have no stop of their own, and their contextual +help "(i)" hints are attached to the pages' headings the way the trigger's and the signals' are: they +are drawn while the page is open, and the dock has none. The chooser's tour works the same way over its own two pages: its tabs step shows the *Waveforms* page and stepping off it -- in either direction -- brings the *Sources* page back. @@ -1631,9 +1646,10 @@ it holds more than the window the bound became the whole ring, and a zoom-out wa view past the window into data the window was never set to show. A partly filled window simply has an empty left-hand side. -**The panel switches are on the toolbar, and the panes own their own controls.** Cursors, -Measurements and Math channels are icon toggles in the header bar rather than rows in the overflow -menu, and **Reset cursors** sits in the cursors pane beside the mode it resets (it acts on that +**The panel switches are on the toolbar, and the panes own their own controls.** (Since the +slide-over redesign the toolbar keeps only the **Cursors** toggle of the three icons named here, and it +only enables the cursors; the measurements are reached by `m` or the dock's Edit button, and the math +editor from the signal list.) **Reset cursors** sits in the cursors page beside the mode it resets (it acts on that pane's instruments and is only visible while they exist). The rows read, left to right: **window control, trigger, pause** — the trigger and the pause are read against the span on screen, so they sit with the control that sets it — then, on the right, **cursors, measurements, math, sidebar** and **the menu last**. With `HeaderBar::pack_end()` the @@ -2849,7 +2865,7 @@ reaches every widget). The full key table: | `Space` | pause / resume | | `t` | enable / disable trigger mode | | `c` | enable / disable the cursors | -| `m` | go to Measure (scroll the dock to the cursors and the measurements) | +| `m` | open or close the Measurements page (the `measure` slide-over page; the dock's Edit button for the cursors opens the `cursors` page) | | `g` | go to Signals (open or close the signal list, the `signals` slide-over page, also reached by the `+` badge) | | `l` | show / hide the layout editor | | `s` | show / hide the dock | @@ -2970,9 +2986,10 @@ now — they are listed so they are not mistaken for anything else: signal list -- in one scrolling column; with four of them open in a 320 px sidebar every section was short of room, and finding one meant scrolling past the others. The sidebar was then two text tabs (a **Measure** tab and a **Signals** tab, with a source kind's own tabs beside them), and four - tabs did not fit 320 px. It is **one dock** now, with no tab switcher: the cursors and the - measurements in one scroller, shown from the start and insensitive until the stream has signals; `m` - scrolls it to the cursors; the **signal list** is the `signals` slide-over page (`g`, or the `+` badge). The **trigger form** and a **source's own + tabs did not fit 320 px. It is **one dock** now, with no tab switcher: the **readouts** (a compact, + read-only copy of the cursors and of the measurements, each block with an **Edit** button), shown from + the start and insensitive until the stream has signals; the full cursors and measurements panels are the + `cursors` and `measure` slide-over pages (`m` toggles `measure`); the **signal list** is the `signals` slide-over page (`g`, or the `+` badge). The **trigger form** and a **source's own controls** are pages of the slide-over, reached from the badge bar. The trigger pane also has a **locked** mode, which is what a returning user gets: it shows one line saying what it is set to and offers no control that changes the configuration -- its heading has an `[edit]` button to switch to the full pane, and back. The signals have no lock: the @@ -2987,19 +3004,28 @@ now — they are listed so they are not mistaken for anything else: toggle hides a pane any more: cursors-off **disables** its readout and leaves it in place, and trigger-off leaves the trigger form fully editable -- a trigger is configured *before* it is switched on, and the locked view says `stream (trigger off)` instead of the settings -- and the measurements' and math panes' toolbar toggles are gone with the panes. -- **The badge bar / slide-over / dock redesign is in progress; this is its first step, and it deviates - from `docs/superpowers/specs/2026-11-02-ui-badges-dock-design.md` in three known ways.** - (1) The dock keeps the sidebar's **320 px**, not the spec's ~280: the panels it holds were built for - 320, and the width changes when they become compact readouts (step 3). (2) The session file gains the - dock's *shown* state only, not a width -- the dock's width is fixed, so there is nothing to persist - -- and does not store the open slide-over page. (3) The **Trigger badge** shows `off`, `armed` and - `triggered` as the spec says, and two more words, `capturing` (a post-trigger window is filling) and - `disarmed` (trigger on, not armed), so the label never claims a state the engine is not in. The signal badges, the - signal pages and the drag (step 2) are done. Still to - come, in the spec's order: the compact readouts (3), the events - section in the dock, the deletion of the left pane and the Events toggle, and `USER-GUIDE.md` and the - manual's figures (4). Until then the events pane, the header's Events toggle and the manual still - describe the previous layout. +- **The badge bar / slide-over / dock redesign is in progress; steps 1-3 are done, and they deviate + from `docs/superpowers/specs/2026-11-02-ui-badges-dock-design.md` in these known ways.** + (1) The session file gains the dock's *shown* state only, not a width -- the dock's width is + fixed (**280 px**, as the spec says), so there is nothing to persist -- and does not store the open + slide-over page. (2) The **Trigger badge** shows `off`, `armed` and `triggered` as the spec says, and + two more words, `capturing` (a post-trigger window is filling) and `disarmed` (trigger on, not + armed), so the label never claims a state the engine is not in. (3) The **`cursors` and `measure` + pages hold the existing panels unchanged** (the cursors table with its mode, its Reset and its + switch; the measurement list with its `+`, statistics and per-row edit), so the pages carry the + panels' own controls, not a new form. (4) The **measurement editor stays a dialog**: the spec has the + measurement add/edit/remove inside the `measure` page, but the dialog (`open_measure_dialog()`) is + what the `+`, a row's edit button and a click on a dock line all open; the page lists the measurements + and hosts the dialog's entry points. (5) A **dock cursor line carries the full who / A / B / delta + cells** (not one value per line as the spec sketches), in a 5 + 3 x 9 character fixed-width grid: the + **who** cell ellipsizes at **5 characters** -- the width the 280 px pin allows; with 8 the readouts' + minimum width was 304 px and widened the sidebar -- and its tooltip carries the full text. (6) The dock and + the pages **share one string** for every value (see `docs/claude/invariants-readouts-and-cursors.md`). + The signal badges, the signal pages and the drag (step 2) are done. Still to come, in the spec's + order: the events section in the dock, the deletion of the left pane and the Events toggle, and + `USER-GUIDE.md` and the manual's figures (4). Until then the events pane, the header's Events toggle + and the manual (which still describes the side panel's tabs, and the cursors and measurements as + panes of it) still describe the previous layout. - **A plot holds at most eight signals** (`RowSet::kSlotsPerRow`), because a `Plot` cannot give up a trace and the slots are therefore created up front. - **One toggle drives both cursor orientations.** The vertical markers and the diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md index 6f9f31b..549fa36 100644 --- a/docs/claude/architecture.md +++ b/docs/claude/architecture.md @@ -101,8 +101,11 @@ touching the data path. a `slideover_add_page(app, id, title, widget)`: the widget is wrapped in a `GtkScrolledWindow` of its own and registered under its id (`trigger`, `source-`, `signals` (the signal list) and `signal:` (one signal's page, `src/ui_signal_page.cpp`, created on demand by `signal_page_ensure()` - and pruned by `signal_pages_sync()`, which `rebuild_signal_panels()` ends with); `measure`, `cursors` - and `events` are reserved for later steps). One page is open at a time and `App::slideover.page` + and pruned by `signal_pages_sync()`, which `rebuild_signal_panels()` ends with), `cursors` (the cursors + panel, unchanged) and `measure` (the measurements panel, unchanged; `m` toggles it); `events` is reserved + for a later step). Both of those are registered whether or not they are open (a closed page's widgets + exist, unmapped), and both are insensitive until the stream has signals, by the same + `update_signal_availability()` call that sets the dock's sensitivity. One page is open at a time and `App::slideover.page` is its id, empty when closed. The open page is **not** a session value. Esc, *Close*, a second click on the badge and a click on the plot close it; the click-away is a capture-phase observer (see [invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). With two or more `source-*` pages, an @@ -116,9 +119,18 @@ touching the data path. file) leaves it **hidden** (the user asked for hide here, not the usual disable): `sync_badge_bar()` sets its visibility, pick and focus from `slideover.source_pages` being non-empty every tick, so anything that indexes the badge (the tour's `badges` step targets the whole bar, `[badges]`) must tolerate it being absent. -- **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`): the - cursors and the measurements only, in one scroller (the signal list is the `signals` page). It holds no tab switcher and no - revealer, and it does not hold a source kind's panels (those are slide-over pages). +- **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`), **280 px** + and fixed. It holds one thing, the **Readouts** box (`src/ui_dock.cpp`, `build_readouts_dock()`): a + **Cursors** block (heading, an **Edit** button that opens the `cursors` page, a `GtkGrid` of a header row + `A B delta` and one line per page line) and a **Measurements** block (heading, **Edit** opening the + `measure` page, one line per measurement: chip, type, value; a click on a line opens that measurement's + dialog; no measurements shows one dim "no measurements" line). It is read-only and **owns no formatting**: + `update_cursors_pane()` and `update_measures()` hand the string they wrote to the page's label to + `dock_cursor_cell()` / `dock_measure_value()` as well, and `rebuild_cursors_pane()` / + `rebuild_measures_pane()` end by rebuilding the dock's lines to match (`dock_rebuild_cursors()`, + `dock_rebuild_measures()`). It holds no tab switcher, no revealer and none of the full panels, nor a source + kind's panels (those are slide-over pages). The window's measurement editor is a dialog + (`open_measure_dialog()`), not part of the `measure` page. **When each is built and when it is read.** `build_slideover()` runs in `on_activate` right after the tour overlay is built (the tour's overlay wraps the whole window, so its dim rectangles cover the slide-over diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index 2fa3653..a7e43a1 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -50,7 +50,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear runs after the window is destroyed and asking GTK about a widget then is four criticals, which is how the first version of that line earned them. What stays in the menu is what acts on the *data* (Fit, Export, Configurations) and the one editor that is not a pane (the layout editor): the signal list - is a section of the sidebar's dock, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text. + is the `signals` slide-over page, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text. - **Five of this app's icons are its own, and they are a `GResource` compiled into the binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a measurement ruler, and the stock names those buttons used to carry said something else @@ -225,13 +225,15 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear take room has to be hidden and not merely unrevealed; the revealer keeps the slide while the pane *is* open. The allocated width is how this was found -- the revealer's own measure said 0. **The sidebar is one dock now, and has no `GtkRevealer` left in it**: a single `GtkScrolledWindow` - (`dock_scroller`) holding the cursors and the measurements one under another. It + (`dock_scroller`) holding only the compact **readouts** (`src/ui_dock.cpp`). It replaced an `AdwViewSwitcher` over an `AdwViewStack`, whose four text tabs did not fit 320 px (and a source can add more); a source kind's own panels are slide-over pages, not dock sections. The reason is the one - this trap teaches: sections competing for one 320 px column each left every one of them short of room, and - a scroller gives each its natural height. `m` scrolls the dock to the cursors (`dock_show_section()`); the signal list is - not a dock section but the `signals` slide-over page, which `g` and the `+` badge open. A *feature's* on/off is no longer a visibility at all -- the - **cursors'** toggle sets its pane's **body** insensitive -- the readout has nothing to show with the + this trap teaches: full panels competing for one narrow column each left every one of them short of room. + The full cursors and measurements panels are now the `cursors` and `measure` **slide-over pages**, reached + by the readouts' **Edit** buttons, and `m` toggles `measure` (it no longer scrolls the dock; + `dock_show_section()` is gone); the signal list is the `signals` slide-over page, which `g` and the `+` badge + open. The header's Cursors toggle no longer opens anything: it only enables the cursors. A *feature's* on/off is no longer a visibility at all -- the + **cursors'** toggle sets its pane's **body** insensitive (and the dock's cursor grid beside it) -- the readout has nothing to show with the instruments off -- and the *body* and not the pane, because the pane's heading carries its own switch (see the next bullet). The **trigger's body is deliberately never disabled**: a trigger is configured *before* it is switched on, so switching it off to change it, or to read what it is set to, is the @@ -243,7 +245,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear - **The dock is shown but insensitive until the stream has signals ("disable, do not hide").** `update_side_visibility()` and the tick's per-frame block set the column's visibility from the user's wish alone (`sidebar_wanted`) and the dock's *sensitivity* from `have_signals`; the toolbar's Dock button stays - sensitive throughout. The wish is persisted (`dock = true|false` in the session file) and a change of it + sensitive throughout. The `cursors` and `measure` **pages follow the same condition** through + `update_signal_availability()` (called from `update_side_visibility()` and the per-frame block), so a + page opened by `m` before the stream has signals is insensitive, not refused. `[readouts] ... pages_sensitive=` + reads it. The wish is persisted (`dock = true|false` in the session file) and a change of it schedules a save. `dock:` in the dump reads the result. - **The signal badge is the drag handle, and a drop handler only moves widgets.** Dragging a signal onto a plot starts on its badge in the badge bar (a `GtkDragSource` on the badge's button, payload the signal index as a @@ -584,6 +589,18 @@ The `Help` submenu, the contextual help's "?" and the guided tour, and the traps `ui_impl::hint_button()` reached either by the table's `context_help_sync()` or by `context_help_button()` for a window with no `App` (the source chooser) -- one button, two callers, so the two windows cannot drift into two affordances. +- **A hint on a slide-over page's heading is attached all the time and drawn only while the page is open.** + The trigger's, signals', cursors' and measurements' headings (`trigger_head`, `signals_head`, + `cursors_head`, `measures_head`) live on pages that exist, unmapped, whether or not they are open, so + `context_help_sync()` attaches their "(i)" like any other and the count (`hints=12`) does not depend on + which page is up. There is **no second code path** and the dock's readouts carry no hint of their own: the + pages' hints say what the dock shows too (the cursors' and measurements' bodies mention the read-only copy + and the Edit button). A probe that must *see* one drawn needs its page open, like every other reading off a page. +- **The tour has one stop for the readouts and none for the pages behind them.** The dock's stop + (`side`, target `side_box`, no `page`, so it closes the panel) describes the readouts, the **Edit** + buttons and the click on a measurement; the `cursors` and `measure` pages are reached from there rather than + walked. A stop for either would be one more `TourStop::page` (see the next bullet), and the plan's + length is asserted (`steps=6`, `steps=5` with the dock put away) -- a new stop changes both. - **The guided tour takes no grab, and that is the whole reason it is an overlay.** Four dim rectangles around the hole are ordinary widgets that swallow the clicks that land on them and nothing else; `Skip`, `Finish` and `Escape` hide them, and there is no popover, no gesture and no diff --git a/docs/claude/invariants-plot-and-layout.md b/docs/claude/invariants-plot-and-layout.md index 89c4ac3..2578344 100644 --- a/docs/claude/invariants-plot-and-layout.md +++ b/docs/claude/invariants-plot-and-layout.md @@ -12,9 +12,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear from the paned's own width, re-parenting the column, the property form of its children, size requests): the *mechanism* was the fault. It is an **`AdwOverlaySplitView`** now — what a utility sidebar is in GNOME, as against `AdwNavigationSplitView`, which is for navigation lists — with `min-sidebar-width` = - `max-sidebar-width` = 320 in **pixels**, so the width is its own at any window size, and `collapsed` (set + `max-sidebar-width` = 280 in **pixels**, so the width is its own at any window size, and `collapsed` (set from the window's width in `tick()`) to *overlay* the plot when there is no room beside it rather than - being squeezed. `[sidebar-probe]` reads both states: `window=756 sidebar=320` under a 2x scale is exactly + being squeezed. `[sidebar-probe]` reads both states: `window=756 sidebar=280` under a 2x scale is exactly the case that used to be zero. - **The sidebar's button is the only input; `AdwOverlaySplitView:show-sidebar` is an output.** The split view writes that property *itself* — at startup, and when it collapses — with no user involved, so reading @@ -29,7 +29,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear no-signals case must leave the wish alone, or a source connecting later finds a sidebar the user never closed. The wish is the session's `dock` key: a change of it (not a program's own write, and not a restore) schedules a save. - **`AdwOverlaySplitView:sidebar-width-fraction` is a 0..1 fraction of the window, whatever - `sidebar-width-unit` says.** Setting a pixel count there (320) is a `GObject` critical, and it silently + `sidebar-width-unit` says.** Setting a pixel count there (280) is a `GObject` critical, and it silently falls back to 0.25 — which reads as "the sidebar is mysteriously 560 px wide" on a wide window with no error anywhere the build would catch. Pin the width with `min-sidebar-width`/`max-sidebar-width`. - **A `GtkPaned`'s position is stated after both its sides are filled**, and that is a fact about the @@ -130,9 +130,16 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear `bar_min_w` reads the same with one signal, nine signals and the overflow (244 on the E-1608, 124 on UDPS: the Source badge is on screen for the E-1608 and hidden for UDPS, and its text differs by kind). A change that makes it grow with the signal count has taken the scroller out of the measurement. -- **The dock's width is fixed too** (`side_column` `width-request` 300, the split view's 320 px floor - and cap), and it holds one scroller with the cursors and the measurements. A - section's visibility is never what makes the dock taller; the scroller is what absorbs the extra. +- **The dock's width is fixed too, and it is the readouts' width that holds it there** (`side_column` + `width-request` 260, the split view's 280 px floor and cap). The dock holds only the compact readouts, so a + section's visibility is never what makes it taller; the scroller absorbs the rest. **The readouts' minimum + width is the sum of their cells' character widths, and it must stay under 280** (the Readouts box is 268: + 280 less its 6 px margins): a cursor line is the `who` cell (5 characters) plus three value cells (9 each), + every one ellipsized at that width (`width-chars` = `max-width-chars`), so a long value is cut and its + tooltip carries the text and the layout never depends on what a value reads. The `who` cell was 8 + characters, which made the readouts' minimum width 304 px; that pushed the sidebar to 304 px + (`dock_w=304`) and defeated the 280 pin. Widening any cell is the same + trap -- read `[readouts] ... dock_w=` (must be 280) and the `dock cursors:` / `dock measures:` `fits=`. - **Trace slots are identified by signal name, not channel index.** Enabling a signal or a new CONFIG renumbers the channel list, so an index would silently re-point a trace at another signal. diff --git a/docs/claude/invariants-readouts-and-cursors.md b/docs/claude/invariants-readouts-and-cursors.md index 45a9e9c..20c36bf 100644 --- a/docs/claude/invariants-readouts-and-cursors.md +++ b/docs/claude/invariants-readouts-and-cursors.md @@ -41,6 +41,22 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear window's **first sample** and never from history before the window, so a measurement never depends on data outside the window it reports on: the left-edge step transient is removed by the seeding, not by priming from earlier samples. +- **The dock's readouts and the pages' panels are one string, never two.** The dock (`src/ui_dock.cpp`) + shows a read-only copy of the cursors table and of the measurement values, and it must not format + anything: `update_cursors_pane()` and `update_measures()` format each value **once**, into a local, and + hand that same variable to the page's label **and** to `dock_cursor_cell()` / `dock_measure_value()` -- + on every branch, including the `"--"` blanking one (cursors off, no sample). A second formatter in the + dock would be two spellings of one reading that drift the first time `format_measure()` changes, and + the dock exists so that the two can be read side by side. The `who` cell is copied from the page's label + verbatim (markup chip and name), and the dock's lines are rebuilt from the page's whenever the page + rebuilds (`dock_rebuild_cursors()`, `dock_rebuild_measures()`), so the line counts agree by + construction. A cell is **ellipsized at a fixed character count** (who 5, values 9, measurement type 12) with + the full text in its tooltip: the dock is 280 px wide and its layout must not depend on what a value + reads, so a long value is cut and quoted whole in the tooltip rather than widening the column (the + `who` cell was 8 characters, which made the readouts 304 px wide and pushed the sidebar past its + pin). `[cursors-probe] ... dock cursors:` and `[measures-probe] ... dock measures:` assert it: `same_text=1` + compares `gtk_label_get_text()` (the untruncated text) of every dock label with the page's, and + `fits=1` that the Readouts box is no wider than the dock. - **`format_measure()` is the one formatter for both panes, and its SI prefix does not depend on there being a unit.** The cursors pane's values and the measurements pane's `text=` go through it, so a change reaches both. The prefix ladder used to be inside `if (!unit.empty())`, which @@ -55,7 +71,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear unit (`636.62 mV`). Below `1e-12` the ladder runs out, and `%.2f` would print `0.00` again, so that range falls back to `%.3g`. - **With the instruments off the readout blanks, and the panel stays on screen.** Cursors-off makes the - pane *insensitive* rather than hiding it -- a panel you can still see is a panel you can read, and the + pane *insensitive* (the page's body, and the dock's cursor grid beside it) rather than hiding it -- a panel you can still see is a panel you can read, and the settings you are about to switch back on must not vanish while you look for them -- and every cell in it is written `"--"`, the spelling an unavailable cell already uses. A readout quotes a stored sample; with nothing bracketing one there is no sample to quote, and keeping the last values would be quoting diff --git a/docs/claude/invariants-session-and-export.md b/docs/claude/invariants-session-and-export.md index 27cc817..bb0b23a 100644 --- a/docs/claude/invariants-session-and-export.md +++ b/docs/claude/invariants-session-and-export.md @@ -31,7 +31,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear toggle; written as a bare top-level boolean (`measure_stats = true/false`), defaults to `false` when absent, and **`dock`** -- `LayoutState::dock_shown`, the Dock button's state (the user's last word on it, `App::sidebar_wanted`): a bare top-level boolean written where `side_tab` used to be, default `true`. - It is the **only** thing the dock persists -- its 320 px width is fixed, so there is nothing to store -- + It is the **only** thing the dock persists -- its 280 px width is fixed, so there is nothing to store -- and **the open slide-over page is deliberately not persisted** (a panel that opens by itself at start-up is a surprise, and a page id from another source kind may not exist). The parser ignores unknown keys (its key chain has no erroring final `else`), so a file written before the tabs became a dock diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index bc8ce06..f405ff7 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -29,7 +29,7 @@ Two lines of the self-check output are assertions in disguise: window slides over a round number. The companion `axes.w changes: N (last at frame F, side panel up=S)` line separates the two readings: `0`, or a handful near the start, is a settled layout; a count near the frame count is the twitch. Runs far longer than - the documented probes have shown one ~320 px step — the side panel's width — that + the documented probes have shown one ~320 px step — the side panel's width, when it was 320 px — that reverts; it is intermittent and unexplained, so read `changes` before calling a range the twitch bug. - `[trig-probe] ... trigger=on ... display=... epoch=...` (stderr, @@ -425,8 +425,8 @@ Two lines of the self-check output are assertions in disguise: — that is how it tells a gesture from its own echo — so a fit that wrote the group alone reads back as the zoomed range one frame later. - `[layout-probe] rows=N buttons=B done=D done_box=[x,y,WxH] rows_y=[..] close=[..] add=[..] traces=[..]` - `side_w` is the sidebar column's width and **must be its own request (320), never 0**: it read 0 while - the divider sat at the window's right edge, which is a panel that is not there. It was 320 during the + `side_w` is the sidebar column's width and **must be its own request (280), never 0**: it read 0 while + the divider sat at the window's right edge, which is a panel that is not there. It was 280 during the first frames and 0 once the window had settled, so read it from a `placed` line rather than the first. (stderr, `UDPSCOPE_PROBE_LAYOUT_MS`) — **`buttons` must be `2N+1`** while the editor is up (one `×` per row, one `+` per boundary) and **0 on the `editor off` line**: the @@ -479,16 +479,30 @@ Two lines of the self-check output are assertions in disguise: substitutes a missing-image placeholder, the button still works, and every other line of the dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this is the only check that it names something that exists. -- `dock: shown=1 sensitive=1 w=320 scroller_pages=1` (in the exit dump, beside `panels:`) -- the sidebar's dock - as the last tick found it. It replaces the old `switcher:` line: there is no tab switcher, the sidebar is - one scroller (`dock_scroller`) holding the signal list, the cursors and the measurements. **`shown=1` +- `dock: shown=1 sensitive=1 w=280 scroller_pages=1` (in the exit dump, beside `panels:`) -- the sidebar's dock + as the last tick found it. There is no tab switcher: the sidebar is one scroller (`dock_scroller`) holding the + **readouts box only** -- a compact, read-only copy of the cursors and of the measurements, each with an Edit + button (`build_readouts_dock()` in `src/ui_dock.cpp`). The signal list, the full cursors table, the measurements + and the trigger are slide-over pages. **`shown=1` always** (the dock is on screen from the start; `shown=0` only when the user hid it with the Dock button, which a probe does deliberately); **`sensitive=0` only before the stream has signals** ("disable, do not hide") -- a run against a port with no sender reads `shown=1 sensitive=0`, a normal run `sensitive=1`; - `w=` is the sidebar's own width (320); **`scroller_pages=1`** counts the scrollers directly under the dock + `w=` is the sidebar's own width (**280**); **`scroller_pages=1`** counts the scrollers directly under the dock column and says there is one scroller and no stack. These are captured on the tick or at the exit probe: the dump runs after the window is destroyed. A source with extra panels adds *no* dock section: its panels are slide-over pages (`source-`), so the line reads the same on the E-1608 and the probe kind. +- `[readouts] pages cursors=1 measure=1 dock_children=1 dock_w=280 pages_sensitive=1 signals=1` (in the exit + dump; `dock_children` counts the direct children of the dock page) -- the two pages and the dock against the + stream. **`cursors=1 measure=1`**: both slide-over pages are registered, always and from the start (not when + the stream has signals); `0` is a page that was never added, and then the dock's Edit buttons and the `m` key + open nothing. **`dock_w=280`** is the sidebar's width, as for `dock:`. **`pages_sensitive` must equal + `signals`**: both pages are insensitive until the stream has signals ("disable, do not hide"; + `update_signal_availability()` writes them with the dock), so a run against a port with no sender reads + `pages_sensitive=0 signals=0`, a normal run `pages_sensitive=1 signals=1`. `pages_sensitive=1` with `signals=0` + is a page that can be opened and read with nothing to read; `pages_sensitive=0` with `signals=1` is a + stream whose pages never came back after the signal list arrived. **`dock_children=1`**: the dock + page holds the readouts box and nothing else (a second child is a section that crept back into the dock; 0 is a + dock that was built empty). - `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=6 step=0 auto=1 requested=0` (in the exit dump, beside `dock:`) — the help affordance layer as the last tick found it. **`hints=` must equal the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window @@ -988,7 +1002,7 @@ two also catch a row whose accelerator went missing. `settings=` must be a real as a placement bug, on the plot stop (the target is the whole content) and on the trigger stop at the documented 756 px window (the panel is 360 px of it, so the card has no side wide enough and goes flush left over the plot, overlapping the panel's edge by a few pixels), and on the `signals` stop for the same - reason (the dock beside it is 320 px and the card has no side wide enough). + reason (the dock beside it is 280 px and the card has no side wide enough). - `[toursurface] finished: running=0 slideover=none` -- the last Next ended the walk and **the panel is closed with it**. - `[toursurface] restarted at trigger: running=1 slideover=trigger` then @@ -999,8 +1013,8 @@ two also catch a row whose accelerator went missing. `settings=` must be a real is how a probe puts the dock away for good): the dock's own stop is dropped, **and the trigger and signals stops are kept** because the slide-over does not depend on the dock -- five stops remain (plot, badges, trigger, signals, status). -- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: dock 0->243 (cursors_y=243 reach=2256 at_cursors=1) | g: page=1 dock_still=1 closed=1 | … | esc: opened=1 handled=1 closed=1 idle_handled=0 | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` is not a pair: it opens the `signals` slide-over page and a second press closes it), **`refused` must be 1**, and the `esc:` reading must be `opened=1 handled=1 closed=1 idle_handled=0`. - The `m`/`g` readings are the dock scroller's vertical adjustment before and after (the probe pads the dock to 3000 px so there is something to scroll, and undoes it at the end): **`m` must land on `cursors_y`, the cursors panel's y in the dock (`at_cursors=1`), and `g` must read `page=1 dock_still=1 closed=1`** (the `signals` page opened, the dock's scroll untouched, a second press closing it), which is what says `m` scrolls to a section and `g` opens the list's page rather than toggling a pane; a `reach` of 0 would mean the dock fit its viewport and the probe's padding was lost; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. **The `esc:` reading goes through `on_app_key_pressed()` with `GDK_KEY_Escape`**, the handler the window's key controller calls, and not through `slideover_escape()`: the probe opens the `trigger` slide-over page (`opened=1`), sends the key (`handled=1`: it was consumed, and `closed=1`: the page is gone), then sends it again with nothing open (`idle_handled=0`: Escape is left alone when there is nothing for it to close, so a dialog or popover under it still gets it). `handled=0` or `closed=0` is the slide-over's Escape never reaching the panel from the window's key path -- a handler that returns before the slide-over test, or a controller in the wrong phase -- which the `[slideover] esc_closes=1` reading (a direct call to `slideover_escape()`) cannot see. **Limit of the `m`/`g` readings:** the padding is 3000 px at the end of the page, so `reach` is non-zero on any window shorter than that and the key has somewhere to scroll, but it does not push the cursors panel out of the viewport -- on a window whose dock viewport is taller than `cursors_y` (a tall window) the section is already in view at offset 0 and `m` still scrolls to it; `at_cursors=1` is the key landing the section's top at the viewport's top, which is the reading, not "the section was hidden before". The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `: : ->` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values). +- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: page=1 dock_still=1 closed=1 | g: page=1 dock_still=1 closed=1 | … | esc: opened=1 handled=1 closed=1 idle_handled=0 | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`m` and `g` are not pairs: each opens a slide-over page, `measure` and `signals`, and a second press closes it), **`refused` must be 1**, and the `esc:` reading must be `opened=1 handled=1 closed=1 idle_handled=0`. + The `m`/`g` readings are `page=P dock_still=D closed=C`: **all three must be 1 for both keys** -- `P` the page the key opens (`measure` for `m`, `signals` for `g`) is the slide-over's open page after the first press, `D` the dock scroller's vertical adjustment did not move (the dock holds only the readouts, so a key must not scroll it), and `C` a second press closes the page. This is what says `m` opens the measurements' page rather than scrolling the dock to a section (the old `m: dock 0->N (cursors_y=… at_cursors=1)` reading is gone with the dock sections), and that neither key toggles a pane. `m` does *not* turn the measurements on or off: the header Cursors toggle only enables cursors, and `m` only opens the page. `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. **The `esc:` reading goes through `on_app_key_pressed()` with `GDK_KEY_Escape`**, the handler the window's key controller calls, and not through `slideover_escape()`: the probe opens the `trigger` slide-over page (`opened=1`), sends the key (`handled=1`: it was consumed, and `closed=1`: the page is gone), then sends it again with nothing open (`idle_handled=0`: Escape is left alone when there is nothing for it to close, so a dialog or popover under it still gets it). `handled=0` or `closed=0` is the slide-over's Escape never reaching the panel from the window's key path -- a handler that returns before the slide-over test, or a controller in the wrong phase -- which the `[slideover] esc_closes=1` reading (a direct call to `slideover_escape()`) cannot see. The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `: : ->` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values). - `[color] '' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit. - `[reseq-probe] reseq=N late=N stale=N overflows=N holes=N hole_slots=N backfilled=N gaps_refused=N open_holes=N bands=N bands_repaired=N nan_slots=N scanned=N trigger_in_hole=D` @@ -1791,11 +1805,26 @@ disagree. The two lines are the claim, in both directions: - `trigger panel sensitive …=0` is the *panel body*, not the panel: the heading row, with its switch, stays live. `1` here while trigger mode is off is the pane not being disabled at all. +## `dock cursors:` and `dock measures:` + +``` +[cursors-probe] dock cursors: lines=1 page_lines=1 same_text=1 dock_w=268 side_w=280 fits=1 dock_sensitive=1 +[measures-probe] dock measures: lines=13 page_rows=13 same_text=1 dock_w=268 side_w=280 fits=1 +``` + +These ride on the `[cursors-probe]` readings (every state line gets a `dock cursors:` beside it) and on the `[measures-probe]` readings (`added`, `settled`, `final`), and read the dock's compact copy against the page it copies. The dock never formats a value: `update_cursors_pane()` and `update_measures()` hand the same string to the page label and the dock label. + +- **`same_text=1` is the assertion**: every dock label (the cursors' who, A, B, delta cells; the measurements' values) reads the same text as the page's label in the same position. `same_text=0` is a second formatter, or a value written to one view only; the two panes must never disagree. The probe compares `gtk_label_get_text()`, so the who cell compares as the text a user reads, markup stripped. +- **`lines` must equal `page_lines` / `page_rows`**: one dock line per page line. A shorter dock is a rebuild that was not mirrored (`dock_rebuild_cursors()` / `dock_rebuild_measures()` run from the pages' rebuilds); `same_text` reads 0 with it, so this names the cause. +- **`fits=1`** (`dock_w <= side_w`, `side_w > 0`): the readouts box fits inside the sidebar scroller. `dock_w` reads **268** inside the 280 px sidebar (the box's own margins and the fixed label widths: who 5 characters, each value 9, a measurement's type 12). **A who cell at 8 characters made the box 304 px and `fits=0`**, which is the defeat of the pin: the fixed-width ellipsis is what makes the dock's layout independent of what a value reads, so a cell given more characters widens the dock for every run, not only for a long name. `side_w=-1` or `dock_w=-1` is a probe that ran before the dock was built. +- **`dock_sensitive=1`** (cursors only): the dock's cursor grid is live whenever the stream has signals; `0` before them ("disable, do not hide"), `0` with signals is the dock never coming back. +- On a session with no cursors line, or with no measurements, `lines=0 page_lines=0 same_text=1` is correct: the dock then shows `no measurements` for the measurements (one non-value label, not counted in `lines`). + ## `[sidebar-probe]` ``` -[sidebar-probe] beside window=756 sidebar=320 collapsed=1 shown=1 button=1(sens=1) -[sidebar-probe] regained window=756 sidebar=320 collapsed=1 shown=1 button=1(sens=1) +[sidebar-probe] beside window=756 sidebar=280 collapsed=1 shown=1 button=1(sens=1) +[sidebar-probe] regained window=756 sidebar=280 collapsed=1 shown=1 button=1(sens=1) ``` `UDPSCOPE_PROBE_SIDEBAR_MS` reads the sidebar in the two states a window can put it in, and against a hide @@ -1806,7 +1835,7 @@ and `regained` reads 400 ms later. - **`sidebar` must never be 0.** It was, for every window narrower than the 940 px the divider used to sit at, and the pane could only be found by dragging the divider back — the report this reading exists for. - It must read its own width (320) in *both* states: `collapsed` changes where the sidebar is, never how + It must read its own width (280) in *both* states: `collapsed` changes where the sidebar is, never how wide it is. - **`shown` and `button` must agree, always.** They are the panel and the toolbar button, and a button that says "shown" over a hidden panel is the report this reading exists for. `shown=1 button=1` on `regained` @@ -1822,24 +1851,22 @@ and `regained` reads 400 ms later. ## `[side-tab-probe]` ``` -[side-tab-probe] section=cursors accums=4 baseline recorded (measure at scroll 2732 of 2732, outside_at_top=1) -[side-tab-probe] section=cursors rows=4 accums=4 advanced=4 min_n=18 +[side-tab-probe] section=none accums=4 baseline recorded (measure page was_open=1, now closed=1) +[side-tab-probe] section=none rows=4 accums=4 advanced=4 min_n=18 ``` -`UDPSCOPE_PROBE_SIDE_TAB_MS` (the variable and the tag keep their pre-dock names; the function is `dock_section_probe`) is the one reading for the rule that **the statistics accumulator keeps running -while the Measure section is out of view**. It scrolls the dock *to* the measurements (`dock_show_section("measure")` lands on the cursors panel, the dock's top, so the probe then moves to the measurements' heading itself), forces one pass so there is a baseline, then scrolls back to the top (the cursors) and reads again two seconds later. To make the scroll observable the probe pads `dock_page` **and the cursors panel** to 3000 px each (probe only, both undone in the report), and it re-arms once after 400 ms so the first reading has the layout. +`UDPSCOPE_PROBE_SIDE_TAB_MS` (the variable and the tag keep their pre-dock names; the function is `dock_section_probe`) is the one reading for the rule that **the statistics accumulator keeps running while the measurements' page is closed**. The measurements are a slide-over page (`measure`), not a dock section, so the old scroll-the-dock set-up is gone. The probe opens the page, forces one pass so there is a baseline, *closes* the page (its widgets stay in the tree, unmapped) and reads again two seconds later. It re-arms once after 400 ms so the first reading has the layout. **Run it against a config that has seen the first-run tour**, which would close the page it opens (`tour_seen = true`). -- **`outside_at_top=1` is the precondition**: with the dock at offset 0, the Measure section (the measurements' heading, below the padded cursors panel) starts at or below the viewport's bottom edge, so the "away from the section" state really has the measurements out of view and `advanced=4` says something. `outside_at_top=0` means the probe's own set-up failed and the run proves nothing -- unpadded, the section's top sits a short way down and the dock's viewport is taller than that. **The limit is the 3000 px:** the guarantee holds for a dock viewport shorter than the padding (any real window); a viewport taller than the cursors panel's padding, about 3000 logical px, would read `0` again. - -- **`advanced=4` is the assertion**: every accumulator gained passes while its section was out of view, and +- **`was_open=1 closed=1` is the precondition**: the page really was open for the baseline and really is closed for the report, so `advanced=4` says something. `was_open=0` is the page never opening (the tour closed it, or the page is insensitive because the stream has no signals) and `closed=0` is `slideover_close()` not taking effect; either way the run proves nothing. `section=` is the slide-over's open page and **reads `none` on both lines**, because the page is closed. +- **`advanced=4` is the assertion**: every accumulator gained passes while the page was closed, and `min_n` (the smallest `n` seen) must be non-zero too. **`advanced=0` is the defect**: something gated the - accumulation on the visible section, so the readout would gain a gap in its min/max/avg for exactly as long as - the trigger was on screen. A `Gtk-CRITICAL` here is the other half of the same rule -- a label or an - allocation read on the unrealized page. + accumulation on the page being open, so the readout would gain a gap in its min/max/avg for exactly as long as + the page was shut. A `Gtk-CRITICAL` here is the other half of the same rule -- a label or an + allocation read on the unmapped page. - The probe enables a signal **and assigns it to a plot** itself, both deliberately. A signal starts disabled (enabling is what starts acquisition), and a channel with no trace leaves the live window *still* -- and a still window is the state `update_measures()` is *supposed* to skip, so a probe that left it that way would - read `advanced=0` for a reason that has nothing to do with the tab. + read `advanced=0` for a reason that has nothing to do with the page. - `rows` is the measurement count and `accums` the accumulator count; the two must agree, because one accumulator per measurement row is what makes `advanced` a statement about every row. diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 719ce17..d47097b 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -423,15 +423,24 @@ precedent, not an exception. pauses, reports again with `dn=` per measurement, then resets and reports a third time. Three phases: `phase=0`, `phase=1` (after pause) and `phase=2` (after Reset). Needs `UDPSCOPE_ENABLE_ALL_MS` ahead of it and `UDPSCOPE_PROBE_MEASURE_MS` to add measurements first. -- `UDPSCOPE_PROBE_SIDE_TAB_MS=3000` with `UDPSCOPE_EXIT_AFTER_MS=11000` — the measurements in the - dock's Measure section, a forced pass, then the dock scrolled back to its top (the - measurements out of view) and read again two seconds later. The probe pads the dock page and the cursors panel to - 3000 px each so the Measure section really starts below the viewport (`outside_at_top=1`) on any real window. **`advanced=4` - is the assertion** (every accumulator gained passes while its section was out of view); `advanced=0` is the - defect where the accumulation is gated on the visible section, which would leave a gap in min/max/avg for - exactly as long as the trigger was on screen. It enables *and plots* its own signal, so it needs no other - probe ahead of it. The reading's own entry is in `probe-readings.md`. (Probe name `dock_section_probe`; the - variable and the `[side-tab-probe]` tag keep their old names so existing scripts keep working.) +- `UDPSCOPE_PROBE_SIDE_TAB_MS=3000` with `UDPSCOPE_EXIT_AFTER_MS=11000` — the measurements while the + `measure` page is **closed**: it adds four measurements, opens the page for a forced pass (the baseline), + closes it, and reads the accumulators again two seconds later. **`advanced=` is the assertion** + (every accumulator gained passes while the page was off screen); `advanced=0` is the defect where the + accumulation is gated on what is visible, which would leave a gap in min/max/avg for exactly as long as the + page was shut. It enables *and plots* its own signal, so it needs no other probe ahead of it, and it + re-arms once after 400 ms (a buffer rebuild between baseline and report reads as a gated pass). It needs a + config that has seen the first-run tour, which would close the page it opens (see the next-but-one bullet). + The reading's own entry is in `probe-readings.md`. (Probe name `dock_section_probe`; the variable and the + `[side-tab-probe]` tag keep their old names so existing scripts keep working. The measurements used to be a + dock section the probe scrolled out of view, with padding; the dock now holds only readouts, so there is + no scroll and no padding.) +- `UDPSCOPE_PROBE_CURSORS_MS` and `UDPSCOPE_PROBE_MEASURES_MS` also print the dock's copy of what they read: + a `dock cursors:` line after every `[cursors-probe]` report and a `dock measures:` line at the + measures probe's added / settled / final stops. **`same_text=1` on every one** is the assertion that the + dock and the page show one string; `fits=1` that the Readouts box is no wider than the dock. Entries in + `probe-readings.md`. The exit chain also prints one `[readouts]` line (pages registered, the dock's one + child, the dock's width, the pages' sensitivity, whether the stream has signals) on every exit-dump run. - `UDPSCOPE_PROBE_DOCK_SESSION_MS=2500` with `UDPSCOPE_EXIT_AFTER_MS=8000` -- the session file's `dock` key, end to end: the dock hidden, saved and reloaded; shown again; and a file from the previous version (`side_tab = "measure"`, no `dock`) loaded without an error. Three `[dock-session]` lines, each `ok=1`; @@ -447,7 +456,7 @@ precedent, not an exception. read `click_inside_closes=0 still_open=1 click_outside_closes=1 esc_closes=1 closed=1`. For a screenshot add `UDPSCOPE_PROBE_SLIDEOVER_HOLD=1`, which leaves the page open after line 1 (run with `GSK_RENDERER=cairo` and `import -window root`): the panel must be drawn over the traces, opaque. -- **Slide-over and page probes (`SLIDEOVER`, `BADGES`, `SIGNAL_PAGES`, `SIGNAL_BADGES`) need a config that has already seen +- **Slide-over and page probes (`SLIDEOVER`, `BADGES`, `SIGNAL_PAGES`, `SIGNAL_BADGES`, `SIDE_TAB`, `KEYS`) need a config that has already seen the first-run tour**: a fresh `XDG_CONFIG_HOME` auto-starts the tour at about 1.5 s, and a tour stop with no page closes the slide-over under the probe. Seed it with `mkdir -p $D/udpscope && printf 'tour_seen = true\n' > $D/udpscope/settings.toml` (or one short throw-away run, which writes the key when the tour starts) and run with `XDG_CONFIG_HOME=$D`. Delete the diff --git a/help/C/org.udpscope.app/cursors.page b/help/C/org.udpscope.app/cursors.page index 6c9eda2..6d51f0b 100644 --- a/help/C/org.udpscope.app/cursors.page +++ b/help/C/org.udpscope.app/cursors.page @@ -25,8 +25,9 @@

Press the cursors button in the toolbar, or c.

-

The pane below the plot shows the readings and owns a Reset button. - Drag a marker to move it.

+

The readings are in the side panel (a read-only copy) and in the cursors panel, which + Edit beside Cursors opens over the plot and which owns a + Reset button. Drag a marker to move it.

The mode dropdown offers four modes: vertical (the shared time pair), diff --git a/help/C/org.udpscope.app/measurements.page b/help/C/org.udpscope.app/measurements.page index 9293d8d..5f0add6 100644 --- a/help/C/org.udpscope.app/measurements.page +++ b/help/C/org.udpscope.app/measurements.page @@ -28,10 +28,10 @@ How to reach it -

Press the measurements button in the toolbar, or m.

+

Press m, or Edit beside Measurements in the side panel.

-

The pane's + adds one; clicking a row opens the dialog where the signal, +

The panel's + adds one; clicking a row opens the dialog where the signal, the type, the threshold share or shares and the on-plot marks are set, with Save and Delete.

diff --git a/help/C/org.udpscope.app/overview.page b/help/C/org.udpscope.app/overview.page index bd95f8b..de4e796 100644 --- a/help/C/org.udpscope.app/overview.page +++ b/help/C/org.udpscope.app/overview.page @@ -88,11 +88,13 @@ The side panel -

Two tabs. Measure holds the cursors readout and the measurements - derived from it; Signals holds the trigger and the channel list, where a - math channel is a row of that one list under a MATH heading whose - + creates one. One row per channel: its colour, an enable switch, its name - and its unit. A channel's badge in the badge bar is dragged onto a plot row.

+

A read-only copy of the cursors and of the measurements; the Edit button + beside each heading opens that instrument's full panel over the plot. The channel + list and the trigger are panels of their own, opened from the badge bar and the + toolbar, where a math channel is a row of the one list under a MATH + heading whose + creates one. One row per channel: its colour, an enable + switch, its name and its unit. A channel's badge in the badge bar is dragged onto a + plot row.

The plot