diff --git a/README.md b/README.md
index af6ee87..6a83df2 100644
--- a/README.md
+++ b/README.md
@@ -2580,7 +2580,8 @@ the only place it is visible. Outside a gesture the plot is not read at all: `cu
snapped value then, which is what the app wrote itself a frame earlier, and adopting that back
is what made a dragged cursor spring to where it had been. A ruler is per row: each row has its own y axis, so two rows' rulers are
two different values, and the pane names the row. In a split row (see "Splitting a row") the vertical pair is drawn in
-every plot and a ruler belongs to one plot, which the pane names `plot 2a`.
+every plot, and so is the horizontal pair: the plots of a split row share one y axis, so the pane lists one ruler
+line for the whole row, and dragging a ruler in any plot moves it in all of them.
`mix` needs no rulers: it reports the value the trace actually has at each marker, read
through the library's `Plot::crosshair_value()` — **the nearer of the two bracketing
@@ -2826,11 +2827,15 @@ reach every position the two-per-row reading offers.
A row can be split into **two to four plots side by side**, up to **eight plots in all**. A split is
only an arrangement of more plots on the screen: **every plot shares the one time window** -- one x
link group, so a pan or zoom in any plot moves them all; one crosshair, drawn at the same time in every
-plot; and one pair of vertical cursors, at the same two times in every plot. What each plot keeps is its
-own **y axis** (auto or fixed), its own horizontal rulers and its own fit; a wheel or an axis drag acts on
-the plot under the pointer. Only the **bottom** row's plots carry the time labels, and the left margin
+plot; and one pair of vertical cursors, at the same two times in every plot. The plots of a row
+also share **one y range**, the way the rows share one x window: the row has one y axis (auto or fixed),
+drawn with its labels at the left of the first plot only, one pair of horizontal rulers drawn at the same
+value in every plot, and one fit. A wheel, a drag, a box zoom, **Fit all** and the Auto/Fixed dialog act on
+the whole row whichever plot they land in, and an auto-scaled row fits the union of its plots' traces (a plot
+with none is ignored). Only the **bottom** row's plots carry the time labels, and the left margin
holding the y labels is one width for the whole figure, so the plots of a row line up with the rows
-above and below it; a plot whose labels are narrower simply has more margin.
+above and below it; a plot whose labels are narrower simply has more margin. Because only the first plot
+has a margin, the plots of a row get equal plot areas at equal weights.
In the **Layout editor** (`l`):
@@ -2847,8 +2852,8 @@ In the **Layout editor** (`l`):
Plots are named by their row and, in a split row, a letter: **`1`**, **`2a`**..**`2d`**. The names are
what the signals page's *Plot on* buttons (tooltip "row 2, cell 1 of 2"), the status notices ("plotting
-'sine' on plot 2b") and the cursors pane for a split row (`plot 2a`; a plot of an unsplit row stays
-`row N`, counted from 0 as before) show. A signal dragged from its badge lands on the plot under the
+'sine' on plot 2b") and the cursors pane (one ruler line per figure row, `row 2` counted from 1 for a split row; a plot of an
+unsplit row stays `row N`, counted from 0 as before) show. A signal dragged from its badge lands on the plot under the
pointer, with the same free-slot rule as a row. The session file keeps the arrangement (`row` and `width`
on each `[[plot]]`, see "The session layout file").
diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md
index 6ec5201..4962deb 100644
--- a/docs/USER-GUIDE.md
+++ b/docs/USER-GUIDE.md
@@ -87,10 +87,12 @@ up to **4** plots, and the figure up to **8** in all.
of one plot it removes the row.
Every plot shares **one time window**: panning or zooming in any of them moves them all, the crosshair
-sits at the same time in each, and the vertical cursors are the same two times in each. Each plot has
-its **own y axis**, its own horizontal rulers and its own fit, and only the bottom row shows the time
+sits at the same time in each, and the vertical cursors are the same two times in each. The plots of a split
+row also share **one y range**: the y labels are drawn at the left of the first plot only, the
+horizontal rulers are the same two values in every plot, and a y zoom, pan, fit or fixed range in any of
+them applies to the whole row. Only the bottom row shows the time
labels. A plot of a split row is named by its row and a letter, `2a` to `2d`: that is what the signals
-page's **Plot on** buttons, the status line and the cursors readout call it, and a signal dragged
+page's **Plot on** buttons and the status line call it, and a signal dragged
from its badge goes onto the plot under the pointer. The arrangement is saved per source, and a session
saved before rows could be split opens with one plot per row, as it was.
@@ -153,7 +155,7 @@ does not walk it off its snapshot.
The cursors toggle in the header bar (`c`) switches on two instruments per row:
- **vertical** — two times, shared by every row (and every plot of a split row);
-- **horizontal** — two rulers per plot;
+- **horizontal** — two rulers per row (every plot of a split row shows the same two values);
- **both** — the two together;
- **mix** — the two times, read off each *trace* where they cross it.
diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md
index cd1832b..6cddb85 100644
--- a/docs/claude/architecture.md
+++ b/docs/claude/architecture.md
@@ -266,7 +266,8 @@ Layers to keep straight:
`figure_row_count()` counts the rows. The figure owns
everything the plots have to agree on: the x window (one x link group every plot joins),
the live follow (the *group's* — a member's own axis must be `XFollow::Off`, since
- `Figure::link_x()` refuses a `Live` member), the left gutter and the crosshair.
+ `Figure::link_x()` refuses a `Live` member), the y range of each figure row's cells (`set_share_row_y`: a split
+ row has one y axis, one ruler pair and one fit), the left gutter and the crosshair.
- `ExportTable` (`src/export.hpp`) — the visible window as columns, collected from the
snapshot or the live rings and handed to the `.npy`/`.csv` writers. Always the stored
diff --git a/docs/claude/build-and-run.md b/docs/claude/build-and-run.md
index cdf00da..d75f25f 100644
--- a/docs/claude/build-and-run.md
+++ b/docs/claude/build-and-run.md
@@ -281,6 +281,28 @@ Cell widths use the height scheme: a floor of **64 px logical** per cell, the we
the last cell closes the row exactly, and a figure too narrow for the floors shrinks the cells
proportionally and drops the column gap. Only the bottom row's cells carry x labels and the x title
(every other plot gets `XStrip::None`), and the left y gutter stays one maximum over the whole figure.
+
+The cells of a figure row can **share one y range**, symmetric to the x link
+(`docs/superpowers/specs/2026-11-03-split-shared-y-design.md`):
+
+- `Figure::set_share_row_y(bool)`, default off so every existing figure and `tests/test_figure.cpp`
+ read as before; the host turns it on in `RowSet::rebuild_all()` before the cells are populated. When
+ on, the cells of each figure row form an implicit y link group, rebuilt with the cells.
+- Link semantics: `layout()` runs a `sync_y_links()` that adopts a member's changed `y_range` as the
+ group's and pushes it to the others, so a wheel, pan, box zoom or axis drag (all `set_y_range`) needs
+ no controller change; a manual `set_y_range` on a member clears the group's auto flag. A member's own
+ autoscale is suppressed and the group owns the flag: it fits to the union of its members' visible
+ traces (cells with no trace are ignored; a log axis takes the union in axis space), and `fit_all` and
+ a double-click fit the group, not one cell. The group only follows on a laid-out frame, so a caller
+ that sets a range or a fit and reads it back needs one `layout()` in between.
+- `YStrip {Both, None}` and `Plot::set_y_strip_mode`, mirroring `XStrip`: `None` forces the y label
+ width, title width and reserved floor to 0. `Figure::layout` sets `None` on every cell but the first
+ of its row, structurally from the column index and never from content, and skips `None` cells in the
+ common-gutter maximum. A y axis drag can therefore start only in the leftmost cell.
+- **Cell placement is by plot width**: the row's y gutter comes off the row width first, then the cells
+ 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/invariants-plot-and-layout.md b/docs/claude/invariants-plot-and-layout.md
index d3320de..1b6be4b 100644
--- a/docs/claude/invariants-plot-and-layout.md
+++ b/docs/claude/invariants-plot-and-layout.md
@@ -123,27 +123,40 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
y), and the **vertical cursor pair is one shared time drawn in every cell** (`cursors_aligned`).
A split is an arrangement of more plots, not a second time scale: nothing about the time axis changes
(see [invariants-time-axis-and-window.md](invariants-time-axis-and-window.md)).
- - **Per-cell y.** Each cell has its own y axis (auto or fixed), its own horizontal rulers and its own
- fit; a wheel or axis drag acts on the cell under the pointer, an x gesture on any bottom-row cell
- moves the shared window. The cursors pane's ruler label is `plot 2a` for a plot of a split row and
- `row N` (the plot index) for one of an unsplit row, so the pane reads the same as before until a row
- is split.
+ - **One y per figure row.** The cells of a figure row share one y range (the plotter's
+ `Figure::set_share_row_y(true)`, which `rebuild_all()` sets before the cells are populated): one y
+ axis (auto or fixed), one pair of horizontal rulers, one fit. A wheel or axis drag in any cell moves
+ the row, an x gesture on any bottom-row cell moves the shared window. The host therefore acts on the
+ **figure row** of a plot it is given (`set_y_auto`, `set_y_range`, `fit_all`, the Auto/Fixed dialog,
+ `row_unit`, which joins the units of every cell and reads "" if they differ). The rulers are stored on
+ the row's first plot index, copied to the other cells by `sync_cursors()` / `insert_cell()` /
+ `rebuild_all()` and placed in every cell by `place_cursors()`. The cursors pane lists **one ruler line
+ per figure row**: `row N` with N the 1-based figure row for a split row, and the 0-based plot index
+ for an unsplit row (the pane read that before splitting existed). Readouts and measurements stay per
+ trace and per plot index; only the axis is shared.
- **Bottom-row x labels only.** The plotter gives every plot that is not in the last figure row
`XStrip::None`; the cells of the bottom row each keep their strip, so a middle-drag on the axis
works in any of them. The `t0` caption goes on those cells too.
- - **One left gutter.** The y gutter is one maximum for the whole figure with the same reserved floor
- as before (`Figure::set_min_y_gutter()`), **not a per-column one**: a per-column gutter would make a
- cell's width depend on its labels, which "layout must not depend on content" forbids. A cell whose y
- labels are narrower than the gutter simply has a wider margin.
- - **Widths are weights with a floor.** The plotter keeps 64 px logical per cell and a column gap
- (`kColGap`, 6 px); the editor's drag is stricter (below). A weight is a share of what is
- left *after* the floors, so cells of `{3, 1}` read 533:220 in a 756 px figure, not 3:1, and the
- editor's 96 px drag floor is a floor on the *width*, not on the share. Because a cell's width is
+ - **One left gutter, on the first cell only.** The y gutter is one maximum for the whole figure with the
+ same reserved floor as before (`Figure::set_min_y_gutter()`). It is carried by the first cell of each
+ row alone: `Figure::layout` gives every other cell `YStrip::None` from its column index (structurally,
+ never from what it holds), which forces its y label width, title width and reserved floor to 0. A
+ `None` cell contributes 0 to the common maximum, so a split row's non-first cells cannot widen the
+ gutter and a row's gutter does not depend on how many cells it has. A per-column gutter sized by the
+ labels would make a cell's width depend on its content, which "layout must not depend on content"
+ forbids. A row whose labels are narrower than the gutter simply has a wider margin. The weights apply
+ to **plot widths**, not cell rects (below).
+ - **Widths are weights with a floor.** The plotter keeps 64 px logical per plot and a column gap
+ (`kColGap`, 6 px); the editor's drag is stricter (below). The floors and the weights act on **plot widths**: the row's y gutter (carried by the first
+ cell) is taken off the row width first, then the cells share the rest, so equal weights give equal plot
+ areas and the first cell's rect is its gutter plus its plot width. A weight is a share of what is
+ left *after* the gutter and the floors, so cells of `{3, 1}` read 546:207 (rects) in a 756 px
+ figure, not 3:1, and the editor's 96 px drag floor is a floor on the plot *width*, not on the share. Because a cell's width is
`64 + extra * weight / total` and not proportional to its weight, `layout_col_apply()` does not scale
the pair's weights by the width ratio: it keeps the pair's weight sum `S` from the drag's *start*
(`layout_col_drag_start`) and gives the left cell `S * (want_left - 64) / (pair - 128)`, clamped to
`[0.001, 0.999]` (the plotter refuses non-positive weights; `0.5` if the pair is not wider than 128).
- `want_left` is the pointer's width clamped to `[96, pair - 96]` logical px. The divider therefore
+ `want_left` and the pair are plot widths (`layout_col_drag_begin` subtracts the first cell's `y_axis_width + y_title_width` from its rect); `want_left` is the pointer's width clamped to `[96, pair - 96]` logical px. The divider therefore
follows the pointer, the shrinking cell reads 96 px at the extreme and the pair keeps its total width
(the split probe's `col-drag` line asserts all three). `kPlotterMinCellPx` (64) mirrors the plotter's
`kMinCellWidthLogical`; both are logical px (the plotter scales it by `ui_scale`, which is
diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md
index fd7c6fc..561f4f0 100644
--- a/docs/claude/probe-readings.md
+++ b/docs/claude/probe-readings.md
@@ -2853,19 +2853,19 @@ inside the panel does not close it), `click_outside_closes=1` (a click on the pl
```
[split-probe] start plots=1 frows=1 cells=[1] widths=[756] tile=1 x_equal=1 cursors_aligned=1 weights=[-] traces=[1:sine,square,ramp,noise]
-[split-probe] split plots=2 frows=1 cells=[2] widths=[376,376] tile=1 x_equal=1 cursors_aligned=1 weights=[-] traces=[1a:sine|1b:square]
-[split-probe] weights 3:1 plots=2 frows=1 cells=[2] widths=[533,220] tile=1 x_equal=1 cursors_aligned=1 weights=[3,1] traces=[1a:sine|1b:square]
+[split-probe] split plots=2 frows=1 cells=[2] widths=[403,350] tile=1 x_equal=1 cursors_aligned=1 weights=[-] traces=[1a:sine|1b:square]
+[split-probe] weights 3:1 plots=2 frows=1 cells=[2] widths=[546,207] tile=1 x_equal=1 cursors_aligned=1 weights=[3,1] traces=[1a:sine|1b:square]
[split-probe] caps-calls insert_cell=[1,1,0] refused_cell=1 insert_row=[1,1,0] refused_row=1 plots=8
[split-probe] roundtrip rt=1 expect=r0(h=2.0000)[w=1.0000:sine] r1(h=1.0000)[w=3.0000:square][w=1.0000:ramp] got=r0(h=2.0000)[w=1.0000:sine] r1(h=1.0000)[w=3.0000:square][w=1.0000:ramp]
[split-probe] ruler-labels split=[row 1,row 2] mixed=[row 0,row 2]
[split-probe] shared-y y_shared=1 y_gutter=1 y_fit=1 ruler_mirror=1 ranges=[A=-3.000000..7.000000 B=-3.000000..7.000000 | fit A=-1.04..1.04 B=-1.04..1.04 data=-1..1] axes_w=[642.4,642.4] auto=1
```
-`UDPSCOPE_PROBE_SPLIT_MS` (stderr; see [verification.md](verification.md)). Nineteen steps, 250 ms apart;
+`UDPSCOPE_PROBE_SPLIT_MS` (stderr; see [verification.md](verification.md)). 23 steps, 250 ms apart;
each restructures and the *next* step reports the frame that was laid out with it, so a line's label
names the arrangement the line **reads**, not the call that made it. The state lines share one format:
`plots` (`row_count()`), `frows` (`figure_row_count()`), `cells=[..]` (per figure row), `widths=[..]`
-(logical pixels of each cell, `|` between rows), `weights=[..]` (the stored cell weights, `-` for a row with
+(logical pixels of each cell rect, `|` between rows; the first cell's rect includes the row's y gutter, ~52 px here, and the others have none, so equal weights read `[403,350]` -- equal *plot* widths, not equal rects), `weights=[..]` (the stored cell weights, `-` for a row with
none), `traces=[label:signals|..]` with the labels `plot_label()` gives (`1`, `1a`, `2b`). Every state line
carries the same three assertions:
@@ -2879,22 +2879,22 @@ The other lines, in order:
| line | value it must read | the defect a wrong reading names |
| --- | --- | --- |
-| `start` / `split` | `cells=[1]` then `[2]` with `widths=[376,376]` (equal halves of 756) and the traces on `1a`/`1b`; a second cell starts **empty**, the probe assigns it | a split that moved or dropped the first cell's traces, or unequal default cells |
-| `weights 3:1` | `widths=[533,220]`, **not** 3:1, and `tile=1` | not a defect: the plotter's 64 px floor per cell is taken off first and the weights share the remainder, so the shares read compressed; a result of exactly 3:1 would be the floor missing |
+| `start` / `split` | `cells=[1]` then `[2]` with `widths=[403,350]` (equal plot widths of 350.4 px; the first rect is 52 px wider by the y gutter it alone carries) and the traces on `1a`/`1b`; a second cell starts **empty**, the probe assigns it | a split that moved or dropped the first cell's traces, or unequal default plot widths (`[376,376]` is the pre-sharing reading: a second cell that still has its own gutter) |
+| `weights 3:1` | `widths=[546,207]`, **not** 3:1, and `tile=1` | not a defect: the row's y gutter and the plotter's 64 px floor per plot are taken off first and the weights share the remainder, so the shares read compressed; a result of exactly 3:1 would be the floor missing |
| `row+cell` | `cells=[2,2]`, `weights=[3,1\|-]` | row 0's cell weights lost or moved when a row and a cell were inserted below it (they belong to the figure row) |
| `caps-calls` | `insert_cell=[1,1,0] refused_cell=1 insert_row=[1,1,0] refused_row=1 plots=8` | a cap that is not reached or not held: row 0 starts with two cells and takes two more, refusing the third call at 4; two rows then fit before the plot total reaches 8 and the third is refused; `plots` must stop at **8** |
| `caps` | `plots=8 frows=4 cells=[4,2,1,1]`, `tile=1` | a ninth plot, or a fifth cell in a row |
| `remove` | `plots=7 cells=[3,2,1,1]` | `remove_cell()` on a multi-cell row that removed the row, or that dropped a neighbour's traces |
| `roundtrip-io` | `wrote=1 read=1 plots_in_file=3` | a save that was refused, or a file that lost a `[[plot]]` table |
-| `roundtrip` (state line) | `cells=[1,2] widths=[756\|533,220] weights=[-\|3,1]` and the three traces on `1`, `2a`, `2b` | the arrangement did not survive `collect_layout()` -> file -> `apply_layout()` |
+| `roundtrip` (state line) | `cells=[1,2] widths=[756\|546,207] weights=[-\|3,1]` and the three traces on `1`, `2a`, `2b` | the arrangement did not survive `collect_layout()` -> file -> `apply_layout()` |
| `roundtrip rt=` | **`rt=1`** with `got=` equal to `expect=` (row weights `h=`, cell weights `w=` and each cell's trace, read back at the file's `%.4f`) | **`rt=0`** names the difference: a weight written per plot index instead of per figure row, widths applied before the arrangement (which resets them), or a trace that moved to another cell |
| `legacy-io` | `read=1 plots_in_file=3 row_unset=1` | the stripped file still carrying a `row`, which would make the next line test nothing |
| `legacy` | `cells=[1,1,1]`, `frows=3`, `widths=[756\|756\|756]`, `traces=[1:sine\|2:square\|3:ramp]` | a session from before the split that did not open as one cell per row, or lost its signals |
| `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=[376,376]`, the new `1b` empty | the editor's cell `+` not reaching `insert_cell()`, or the figure not re-pointed after it |
+| `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=[376.5,376.5]->[456.5,296.5]` and `ext=[657.0,96.0\|96.0,657.0]` (all logical px, 753 px pair) | `ok` is the AND of the checks. `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=[96,657]` | the last extreme drag was -10000: the left cell sits on the 96 px floor and the right takes the rest of the pair. A left cell reading 144 is the old floor-ignoring weights (`weights=[0.25,1.75]`); correct weights read `[0.1,1.9]` |
+| `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) |
| `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) |
@@ -2905,7 +2905,7 @@ The other lines, in order:
| `assign-1b` / `drop-2b` | `assigned=1 notice='plotting 'sine' on plot 1b'`; `dropped=1 notice='plotting 'square' on plot 2b'` | a notice that names the plot by number alone |
| `after-assign` (state line) | `traces=[1a:-\|1b:sine\|2a:-\|2b:square]` | the signal went to a neighbouring cell |
| `ruler-labels` | `split=[row 1,row 2]` and, with cells `{1,2}`, `mixed=[row 0,row 2]` | the cursors pane's ruler lines (a label read through the pane's own widgets): **one line per figure row**, because the cells of a split row share their y axis and so their rulers. A split row is `row dt meaningful across
rows.
A ruler belongs to its row and nothing shares it, so two rows' rulers are - independent.
All the plots of a row, and of the whole figure, share one time window: zooming or
panning along time in any of them moves all of them, and the vertical cursors are drawn in
- every plot. The y axes, the horizontal cursors and the fits belong to each plot. Only the
+ every plot. The plots of a split row also share one y range: the y labels are drawn at the
+ left of the first plot only, the horizontal cursors sit at the same values in every plot, and a
+ zoom, pan, fit or fixed range in any of them applies to the whole row. Only the
bottom row carries the time labels. A row holds at most 4 plots and the figure at most 8
in total; the
A plot's own