From 03cb85e015e8ac7046cfd0657543bc9e75eff6b5 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 4 Nov 2026 01:14:36 +0100 Subject: [PATCH] shared-y: final review fixes --- deps/plotter | 2 +- docs/claude/build-and-run.md | 1 + docs/claude/probe-readings.md | 4 ++-- .../specs/2026-11-03-split-shared-y-design.md | 2 +- src/ui_probe.cpp | 12 +++++++++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/deps/plotter b/deps/plotter index 2b4b316..69f0083 160000 --- a/deps/plotter +++ b/deps/plotter @@ -1 +1 @@ -Subproject commit 2b4b316ad87822f85608d30cec402ee5fdfb88a4 +Subproject commit 69f0083fc07c23d466325e119651b6eb9f85a305 diff --git a/docs/claude/build-and-run.md b/docs/claude/build-and-run.md index d75f25f..78799e6 100644 --- a/docs/claude/build-and-run.md +++ b/docs/claude/build-and-run.md @@ -303,6 +303,7 @@ The cells of a figure row can **share one y range**, symmetric to the x link share the rest by the existing `floor + extra * w / total` rule, the floors applying to plot widths. Equal weights give equal plot areas; the first cell's rect is gutter plus its plot width and the rects still tile the row exactly. + In the GTK4 and GLFW adapters the pointer is routed by **cell rect** (`row_in(rows, x, y)`: the containing cell, else the nearest by squared distance), one `input::Controller` per plot. The tests are the `figure_cell*` cases in `tests/test_figure.cpp` and `glfw_host_routes_by_cell`. diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 561f4f0..b13a868 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -2893,8 +2893,8 @@ The other lines, in order: | `invalid-rows` | `ok=1 frows/plots=[5/5 3/3 3/3 3/3]` | one of four unusable `row` sequences (five cells in a row; a skipped row; a row that goes back; a first row that is not 0) that was refused, or kept as a split instead of loading one cell per row | | `editor-on` -> `cell-split` | `cells=[1]` then `cell-split-ck cells=2` and `cell-split` with `widths=[403,350]`, the new `1b` empty | the editor's cell `+` not reaching `insert_cell()`, or the figure not re-pointed after it | | `cell-assigned` | `1a:sine\|1b:square` | an assignment into the new cell that landed on another plot | -| `col-drag` | `ok=1 left_gt_right=1 follows_80=1 relative=1 sum_80=1 right_floor=1 left_floor=1 sum_ext=1 both_above_min=1 tile=1`, then `d80=[350.4,350.4]->[430.4,270.4]`, `ext=[604.8,96.0\|96.0,604.8]` and `gutter0=52.2` (all logical **plot** widths, `axes.w` plus the right gutter of the two plots, measured after a `layout()` on the new weights; 700.8 px pair) | `ok` is the AND of the checks. The compared widths are plot widths and not cell rects, because only the first cell carries the y gutter (`gutter0=` is its rect width minus its plot width, printed so a reader can tell the two apart): a drag that equates the rect widths moves the divider ~17 px more than the pointer (the gutter's share is treated as weight; `d80=[350.4,350.4]->[447.6,253.2]`, +97.2 for +80) and reaches the left floor at 145.5 instead of 96 (`ext=[607.5,93.3\|93.3,607.5]` or `[659.7,93.3\|145.5,607.5]`), which reads `follows_80=0 relative=0 left_floor=0 right_floor=0 both_above_min=0`. `gutter0=` itself should read the y gutter (about 52 px; 0 means the first cell lost its gutter, a value near the cell width means the plot area collapsed). `follows_80=0`: the drag of +80 did not put the left cell within 1 px of `start + 80` (the weights were taken as proportional to widths and the plotter's 64 px floor ignored -- an old reading was `[442.9,310.1]`); `relative=0`: three updates (+30, +60, +80) from one begin did not land at +80, i.e. the drag read the live weights instead of the begin snapshot `layout_col_drag_start`; `sum_80=0` / `sum_ext=0`: the pair's total changed instead of being redistributed; `right_floor=0` / `left_floor=0`: after +10000 / -10000 the shrinking cell does not read `kMinCellPx` (96) within 1 px; `both_above_min=0`: a cell below 96 px; `tile=1` is `split_probe_tile()`, the same check the state lines print. `left_gt_right` is `d80.l > d80.r` (after +80 from equal halves the left is the wider). The probe goes through `layout_col_drag_begin()` + `layout_col_apply()`, the pair the gesture calls | -| `pre-insert0` (state line) | after the extreme drags `widths=[148,605]`, `weights=[0.111732,1.88827]` | the last extreme drag was -10000: the left *plot* sits on the 96 px floor (the rect reads 148 = 96 plus the 52 px y gutter) and the right takes the rest of the pair. A left rect reading 144 is the old floor-ignoring weights (`weights=[0.25,1.75]`); `[146,607]` / `[96,657]` are the readings of the drag before it measured plot widths and before the sharing (floor 93 px / 96 px rect) | +| `col-drag` | `ok=1 left_gt_right=1 follows_80=1 relative=1 sum_80=1 right_floor=1 left_floor=1 sum_ext=1 both_above_min=1 tile=1`, then `d80=[350.4,350.4]->[430.4,270.4]`, `ext=[604.8,96.0\|96.0,604.8]` and `gutter0=52.2` (all logical **plot** widths, `axes.w` plus the right gutter of the two plots, measured after a `layout()` on the new weights; 700.8 px pair) | `ok` is the AND of the checks. The compared widths are plot widths and not cell rects, because only the first cell carries the y gutter (`gutter0=` is its rect width minus its plot width, printed so a reader can tell the two apart): a drag that equates the rect widths moves the divider ~17 px more than the pointer (the gutter's share is treated as weight; `d80=[350.4,350.4]->[447.6,253.2]`, +97.2 for +80) and reaches the floor at 93.3 plot px instead of 96 (`ext=[607.5,93.3\|93.3,607.5]`, still the 700.8 px plot pair; in rect widths, which add the 52.2 px gutter to the left cell, the same wrong drag reads a 145.5 px left rect and a 753.0 px pair, `[659.7,93.3\|145.5,607.5]`), which reads `follows_80=0 relative=0 left_floor=0 right_floor=0 both_above_min=0`. `gutter0=` itself should read the y gutter (about 52 px; 0 means the first cell lost its gutter, a value near the cell width means the plot area collapsed). `follows_80=0`: the drag of +80 did not put the left cell within 1 px of `start + 80` (the weights were taken as proportional to widths and the plotter's 64 px floor ignored -- an old reading was `[442.9,310.1]`); `relative=0`: three updates (+30, +60, +80) from one begin did not land at +80, i.e. the drag read the live weights instead of the begin snapshot `layout_col_drag_start`; `sum_80=0` / `sum_ext=0`: the pair's total changed instead of being redistributed; `right_floor=0` / `left_floor=0`: after +10000 / -10000 the shrinking cell does not read `kMinCellPx` (96) within 1 px; `both_above_min=0`: a cell below 96 px; `tile=1` is `split_probe_tile()`, the same check the state lines print. `left_gt_right` is `d80.l > d80.r` (after +80 from equal halves the left is the wider). The probe goes through `layout_col_drag_begin()` + `layout_col_apply()`, the pair the gesture calls | +| `pre-insert0` (state line) | after the extreme drags `widths=[148,605]`, `weights=[0.111732,1.88827]` | the last extreme drag was -10000: the left *plot* sits on the 96 px floor (the rect reads 148 = 96 plus the 52 px y gutter) and the right takes the rest of the pair. A left rect reading 146 (`widths=[146,607]`, `weights` near `0.1024`) is the 93.3 px plot floor plus the 52.2 px gutter: the drag was measuring cell rects (the gutter's share treated as weight) and not plot widths, so the left plot stopped short of the 96 px floor. The pair still sums to 753 in the baseline and in that reading, so it is the floor, not the sum, that names the defect | | `insert0-move` | `move_ok=1 before=[sine,square] after=[,sine,square]` | an insert at the left edge that renumbered the cells instead of moving their traces right with them | | `remove-mid` | `rm_ok=1 after=[,square]` | removing a middle cell that dropped or shifted the wrong neighbour | | `row-weights` | `wt_ok=1 before=[3,1,1] after=[1,1]` | the row weights not following an insert or removal of a *figure row* (the vector had the plot-count length once and drifted off the rows) | diff --git a/docs/superpowers/specs/2026-11-03-split-shared-y-design.md b/docs/superpowers/specs/2026-11-03-split-shared-y-design.md index 64650e9..06ba826 100644 --- a/docs/superpowers/specs/2026-11-03-split-shared-y-design.md +++ b/docs/superpowers/specs/2026-11-03-split-shared-y-design.md @@ -50,7 +50,7 @@ one x window and draw one x axis. A split row reads as one wide plot with divide `place_cursors`; `sync_cursors` reads the dragged one back; `reset_cursors` and the measure level marks use the row's range. The cursors pane lists the pair once per figure row. - `row_unit` joins the units over all cells of the row, "" if mixed. -- `sync_compare_axes` writes y once per figure row. +- `sync_compare_axes` is unchanged: it writes x and y once per `RowSet` row. The comparison panes are flat single-cell rows, so that is once per figure row and no y sharing is involved. - `rebuild_all` turns the sharing on for the new `Figure` before the cells are populated. - Readouts and measurements stay per trace and per plot index; only the axis is shared. - Session file: unchanged (y ranges were never persisted). diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index 91381cb..dcce21e 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -4473,6 +4473,11 @@ gboolean split_probe_step(gpointer user) { const RowSet::Row& rb = rs.row(1); if (ra.plot == nullptr || rb.plot == nullptr) { std::fprintf(stderr, "[split-probe] shared-y no plots\n"); + // Restore what step 22 restores, then chain on so the rest of the probe still runs. + rs.set_cursor_mode(g_shared_y.was_mode); + rs.set_cursors_visible(g_shared_y.was_on); + rebuild_cursors_pane(*a); + split_probe_next(*a); break; } // 1. The range set on A is B's. @@ -4513,7 +4518,12 @@ gboolean split_probe_step(gpointer user) { case 22: { const RowSet::Row& ra = rs.row(0); const RowSet::Row& rb = rs.row(1); - if (ra.plot == nullptr || rb.plot == nullptr) break; + if (ra.plot == nullptr || rb.plot == nullptr) { + rs.set_cursor_mode(g_shared_y.was_mode); + rs.set_cursors_visible(g_shared_y.was_on); + rebuild_cursors_pane(*a); + break; + } const plot::Range a_rng = ra.plot->y_range(ra.axis_y); const plot::Range b_rng = rb.plot->y_range(rb.axis_y); plot::Range da{};