fix: the math editor's body was a child of the window, not of the box above it

Moving the editor's frame into math-editor.ui left `math_body` and `math_actions` as
<child> elements of the *window* rather than of `math_outer`, because those edits were
anchored on the file's tail and closed the outer box two elements early. A GtkWindow
takes exactly one default <child>, so nothing failed loudly: the body -- with the canvas
and the parameter form inside it -- was left parentless, visible but never mapped, 0x0,
drawing nothing. Two symptoms, one cause: the canvas is blank, and the window is sized
around the head row and the buttons alone.

Measured against the pre-move revision in a worktree, same probe, same environment:
before this fix `canvas=0x0 mapped=0 window=756x940`; after it `canvas=420x836 mapped=1
window=938x940`, which is the old revision's own reading exactly.

It survived because that slice's probe read the editor's *values* (nodes, gain, output,
name, unit) and nothing about its structure. Every other panel moved that day has a
structural reading -- `controls_inside=13/13`, `nested=1`, `labels=2 list=1` -- and none
of them was affected. `[math-probe] editor frame:` is that reading now: the canvas's
allocation, its mapped state, and whether the canvas and the name entry are inside the
dialog at all, delayed 500 ms because an allocation only exists after a layout. In the
broken build it read `0x0 mapped=0 in_dialog=0`.

check_ui passes both files: the XML was valid and every property type-checked.
This commit is contained in:
Martino Ferrari committed 2026-10-12 19:35:39 +02:00
1 parent 0e891ada04
commit c3d588f03d
4 files changed
+48 -3

No files matched your search

+11
View File
@@ -335,3 +335,14 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
reads the *reverse* of the file: `header-bar.ui` declares menu, sidebar, math, measures, cursors and the
screen shows cursors, measures, math, sidebar, menu. The three comments this replaced each had it wrong
once, which is why the order is the file's now and a byte-for-byte diff of the probe is the check.
- **A widget can end up in the wrong container, and only a *structural* read notices.** `math_body` was
written as a `<child>` of the *window* instead of the box above it when the math editor's frame moved into a
`.ui`. A `GtkWindow` takes exactly one default `<child>`, so nothing failed: the body -- with the canvas and
the parameter form inside it -- was left parentless, `visible` but never `mapped`, size `0x0`, drawing
nothing. The symptom was "the editor opens and the canvas is blank", with the window sized around its
remaining children. `check_ui` passes such a file; `xml-stripblanks` passes it; so does every probe that
reads *values* off the widgets inside. What catches it is a reading that walks *up* from the widget
(`in_dialog=1`) or asks whether it was ever given an allocation (`mapped=1`, `canvas=420x836`). When a frame
moves into a file, the probe that proves it has to read the frame's *structure*, not only the numbers it
holds.
+6 -1
View File
@@ -41,7 +41,12 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
anywhere in 78--101 ns/slot is one reading of the same thing.
The probe also asserts the new editor mechanics. `dialog build:` reads the identity row back as well --
`name='math3' unit=''` -- which is the pair the `.ui` holds the widgets for and the code fills from the
channel being edited, so a row that stopped being the one the code writes shows there. Each line carries
channel being edited, so a row that stopped being the one the code writes shows there.
`editor frame:` is the same idea one level down: it reads the canvas's allocation, whether that canvas is
*mapped*, and whether the canvas and the name entry are inside the dialog at all -- delayed by 500 ms, because
an allocation only exists after a layout. A canvas that is visible but parentless never maps and stays `0x0`,
and reading values off the widgets *inside* the editor cannot see that. It is the reading that catches
`math_body` written as a child of the window instead of the box above it. Each line carries
`(want ...)` and the reading
taken from a real run is the reference:
* `[math-probe] hit: inside node=0 which=-1, past-edge node=0 which=-1 (want 0 -1 0 -1)` — the
+2 -2
View File
@@ -46,8 +46,6 @@
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="math_body">
<property name="orientation">horizontal</property>
@@ -80,5 +78,7 @@
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
+29
View File
@@ -386,6 +386,35 @@ gboolean math_probe(gpointer user) {
if (ed->gain != nullptr) gtk_spin_button_set_value(GTK_SPIN_BUTTON(ed->gain), 2.0);
if (ed->offset != nullptr) gtk_spin_button_set_value(GTK_SPIN_BUTTON(ed->offset), 0.0);
on_math_param_changed(nullptr, ed);
// The frame, structurally: the canvas being *in* the dialog and given an allocation. A `.ui`
// mistake this has already paid for once -- `math_body` nested under the window instead of the
// outer box -- leaves the canvas visible but parentless: it never maps, stays 0x0 and draws
// nothing, which a user reads as "the editor opens and the canvas is blank". The values below
// cannot see that, and the reading is delayed because an allocation only exists after a layout.
g_timeout_add(500, +[](gpointer u) -> gboolean {
App* app = static_cast<App*>(u);
if (app->math_dialog == nullptr) {
std::fprintf(stderr, "[math-probe] editor frame: (dialog closed before the reading)\n");
return G_SOURCE_REMOVE;
}
MathDialog* m = static_cast<MathDialog*>(
g_object_get_data(G_OBJECT(app->math_dialog), "udpscope-math-dialog"));
if (m == nullptr) return G_SOURCE_REMOVE;
const auto in_dialog = [app](GtkWidget* w) {
for (GtkWidget* p = w; p != nullptr; p = gtk_widget_get_parent(p)) {
if (p == app->math_dialog) return 1;
}
return 0;
};
std::fprintf(stderr,
"[math-probe] editor frame: canvas=%dx%d mapped=%d in_dialog=%d "
"name_in_dialog=%d\n",
m->canvas != nullptr ? gtk_widget_get_width(m->canvas) : -1,
m->canvas != nullptr ? gtk_widget_get_height(m->canvas) : -1,
m->canvas != nullptr ? gtk_widget_get_mapped(m->canvas) : -1,
in_dialog(m->canvas), in_dialog(m->name));
return G_SOURCE_REMOVE;
}, a);
const std::size_t built = ed->nodes.size();
const double gain_read = ed->nodes.empty() ? 0.0 : ed->nodes.back().gain;
const double offset_read = ed->nodes.empty() ? 0.0 : ed->nodes.back().offset;