git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitc91755c08692f01ce4977df99c9f8352f1effdb1
parentb82c6bfeea
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 21:51
refactor(network): remove the network plate; ctrl+drag box-selects

The network plate was a switch, off by default since this morning. It is
gone now, with every branch it gated reduced to the plate-off one: the
toggle_network_plate command (Shift+P), Action::ToggleNetworkPlate, the
network_plate field on State and in the viewport settings (an old file
naming it still loads), the menu and menubar rows, the plate draws, the
flat focus ring, and the network panels as plates over the params HUD and
over point numbers.

What only the plate reached is replaced or fixed:
- Box selection by mouse is ctrl+drag on empty space, where a plain press
  orbits the camera.
- cursor_in_viewport in overlay mode is bounded by the viewport's rect, not
  the old column split, so the scene under the params HUD's rows can be
  orbited and right-clicked.
- The network's right-edge resize hotspot is off while it spans the window.
- A hidden network has no area, so the middle button pans the camera then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md       | 141 ++++++++++++-----------
 src/app.rs      | 143 +++++++++++-------------
 src/command.rs  |   1 -
 src/dialog.rs   |   1 -
 src/main.rs     | 338 +++++++++++++++++++-------------------------------------
 src/render.rs   |  28 ++---
 src/shortcut.rs |   2 -
 7 files changed, 259 insertions(+), 395 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 40c6b1f..b21ec32 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -339,7 +339,7 @@ gone from cce-ui with the wgpu path).
   (`sync_nodes` + `rebuild_scene_geometry` + `sync_parameters_pane`).
   Input hooks live in `handle_event`: presses intercept in the MouseInput arm
   ahead of the viewport context menu (gated on `cursor_in_viewport() &&
-  !in_network_pane`, so the network plate keeps its clicks where they overlap),
+  !in_network_pane`, so the network keeps its clicks on its nodes),
   motion at the top of CursorMoved, Escape ahead of connection-cancel.
 
   What differs per tool is the `HandleSource` trait: which node types it
@@ -665,7 +665,7 @@ and render blocks of `DesignSettings` without the startup pointer, taken by
 `State::display_settings` (which `save_settings` builds from as well) — and
 both `load_from_file` paths apply it through `apply_display_settings`, before
 the Default Camera view so a camera node's own Pivot still wins. The apply sets every field, regenerates the baked meshes, relays
-the two pane-shaped ones (network plate, circular pane), re-checks the
+the pane-shaped one (the circular pane), re-checks the
 menubar marks, and saves state.kdl, so state.kdl holds the LAST-USED look:
 what New and an older save (no block, which changes nothing) open with.
 Main window only, as the pane state is: a detached window has no viewport
@@ -686,7 +686,7 @@ knowing. `State::new` loads the bundled project, whose meta subnets used to
 be copied over the live viewport flags after every parameter change (the
 meta node is retired, but the hazard was real and this redirect is what
 caught it); so any test that then reached `save_settings` —
-`run_command("toggle_network_plate")`, the dialog's toggle rows — wrote the
+`run_command("toggle_network_plate")` (retired since), the dialog's toggle rows — wrote the
 BUNDLED project's show_grid / show_cube / show_origin over the user's real
 state.kdl. `cargo test` reset three of the user's own toggles on every run,
 and the run was green either way. `Project::load_recent_files` /
@@ -2625,8 +2625,8 @@ answers `color()` with `cce_ui::colors::param_plate_fill`, which is
 `Material::pane()`: the toolkit's PANE rung. So there is ONE material for
 the designer's plates, configured in the `style.surface` block of
 `~/.config/cce/config.kdl` (a `~/.config/cce/cce-designer/config.kdl`
-merges over it key by key, when one exists), and the network plate and the
-params plate cannot be styled apart short of binding a named material
+merges over it key by key, when one exists), and the params plate and the
+spreadsheet's cannot be styled apart short of binding a named material
 (`plate material="…"`). The viewport has no plate of its own: its lip is the
 window's root-plate edge. What the block looks like after the 2026-09-28
 consolidation, and what each key is:
@@ -2698,8 +2698,8 @@ The rules that took a day to settle, each with the wrong version it replaced:
   `solid_border`): without one there is no roll and no tint.
 - **The grid cursor is in the accent only while the network has focus**,
   in the plates' neutral `border_color` otherwise (since 2026-10-05; it
-  was always the accent). With the network plate off it is the network's
-  ONLY focus cue — there is no plate to tint and no edge to ring, the pane
+  was always the accent). Outside the circular pane it is the network's
+  ONLY focus cue — the network has no plate to tint and no edge to ring,
   spanning the window — and with the shader off focus adds a flat accent
   ring around it.
 
@@ -2714,15 +2714,15 @@ Two keys look like they apply and do not: `style.surface.plate.color`
 feeds `plate_color`, whose one consumer is the info box, and the finish's
 spec / shininess / curvature live as `relief.spec` / `.shininess` /
 `.curvature`, not under `plate`. The network pane is two layers: the
-`PassivePlate` above and the Graph on top, which has NO fill of its own
-(since 2026-09-29): its cells are whatever it is painted on — the pane
-plate, or the scene with the plate off — and `style.surface.graph` sets
+`PassivePlate` above (no longer drawn: the network has no plate) and the
+Graph on top, which has NO fill of its own (since 2026-09-29): its cells
+are whatever it is painted on — the scene — and `style.surface.graph` sets
 only the lines (`grid_color`, `line_width`), their `opacity`, and `blur`.
 Until then the graph filled itself with `cell_color` (chosen by a
 `uniform_background` flag this app hard-coded true) and drew its lines in
 `gap_color`; all three keys are retired, reported by path at load and not
 read.
-The next section is about dropping the pane plate entirely.
+The network has no plate at all now: "The network has no plate" below.
 
 **Floors: a faint plate, compressed content** (since 2026-10-06). The way
 to make the plates recede and keep what is on them readable is to compress
@@ -2756,28 +2756,40 @@ relief is two shapes", "Frost is one block"); this is the designer's view of
 them, written because the question "what are the style parameters of the
 plates" took a session to answer from the code.
 
-### The network plate is optional, and off by default (since 2026-10-06)
+### The network has no plate (since 2026-10-06)
 
-The network pane can drop its PLATE — the filled, frosted surface its graph
-sits on — so the nodes and wires overlay the 3D scene directly. The viewport is
+The network's nodes and wires stand directly on the 3D scene. The viewport is
 full-bleed (`CANVAS_IDX` covers the window and the other panes float over it),
-so removing the plate is all it takes: what is behind the pane is the scene.
-
-The pane itself is untouched. It keeps its rect, its focus domain, its corner
-menus, its clip and its keyboard navigation; only two `append_widget_plate_radii`
-calls are skipped — `CONTENT_IDX`'s (the graph's own plate) and
-`NETWORK_PANEL_IDX`'s (the panel behind it). Skipping one and not the other
-leaves a surface, so both are gated on the same flag. Node bodies keep their
-blur-behind fill, which is what makes the result legible: they frost the scene
-behind each node while the gaps stay clear.
-
-**With the plate off the pane spans the whole window.** The dock rect is
-overridden at its source in `rebuild_positions` — one `let (px, py, pw, ph)`,
-so content, panel and breadcrumb all follow — because there is no surface left
-to bound the graph, and one confined to a rectangle you cannot see is worse
-than one that spans what it is drawn over.
-
-That makes the pane's RECT useless as a hit test, and three things route off it:
+so with no plate under the graph what is behind it is the scene. Node bodies
+keep their blur-behind fill, which is what keeps them legible: they frost the
+scene behind each node while the gaps stay clear.
+
+It was a switch for a while — `toggle_network_plate` (Shift+P), the
+`network_plate` field on `State` and in the viewport settings, a row of the
+network menu and the menubar — with the plate on until the morning of
+2026-10-06, off by default that afternoon, and gone that evening, with every
+branch it gated reduced to the plate-off one. A state.kdl or a project's
+display block that still says `network_plate` is read as if it did not (the
+settings structs ignore keys they do not know), and the next save drops it.
+What only the plate reached went with it: the network's flat focus ring
+(the grid cursor carries its focus), the network panels as plates over the
+params HUD (`plates_over_params`) and over a point number (`under_a_plate`).
+The SECOND network editor has no plate either; it keeps its dock rect.
+
+The pane itself is untouched. It keeps its focus domain, its menus, its clip
+and its keyboard navigation, and it is still a dock occupant (its plate menu
+still lists tabs and Move To, which change nothing that is seen while it
+spans the window).
+
+**The pane spans the whole window** (`network_overlay`, which is true while
+the network is shown, not circular and not a detached window). The dock rect
+is overridden at its source in `rebuild_positions` — one
+`let (px, py, pw, ph)`, so content, panel and breadcrumb all follow — because
+there is no surface to bound the graph, and one confined to a rectangle you
+cannot see is worse than one that spans what it is drawn over. Its right-edge
+resize hotspot is off for the same reason (`network_resize_edge_at`).
+
+That makes the pane's RECT useless as a hit test, and these route off it:
 
 - **Clicks** ask `in_network_pane`, which in overlay mode narrows to "a node is
   under the cursor, and no floating pane covers it" (`overlay_claims`). The
@@ -2792,20 +2804,30 @@ That makes the pane's RECT useless as a hit test, and three things route off it:
 - **The wheel** is decided per GESTURE (since 2026-10-06,
   `State::overlay_wheel_to_graph`): one begun on a node pans the graph, one
   begun anywhere else orbits the camera, as a click would. Until then the
-  graph took every scroll by its window-wide rect, so with the plate off a
+  graph took every scroll by its window-wide rect, so without the plate a
   trackpad could not orbit at all. The target is held (`State::overlay_wheel`)
   to the finger's lift, a pause of `OVERLAY_WHEEL_GAP` or the pointer
   moving: a pan slides the node out from under a pointer that does not
   move, and the rest of the swipe would otherwise turn the camera. The
   ctrl zoom follows the same target.
   `a_scroll_over_the_plateless_network_orbits_unless_it_begins_on_a_node`.
-- **`cursor_in_viewport`** becomes the complement: the body, minus what the
-  network holds, minus the floating panes.
-
-What changes for the user: a plain click on empty space is no longer the
-network's — it ORBITS THE CAMERA instead (see below), which is what makes the
-overlay feel like a scene with a graph on it rather than a graph with a
-picture behind it. Deselecting is on Escape.
+- **Box selection is ctrl+drag**: a ctrl+left press on empty space is the
+  network grid's empty-grid press — the cursor to the cell, an expansion drag
+  armed from it, settled on the release (see "The cursor is a region") —
+  where a plain press there orbits. The network takes focus with it.
+- **`cursor_in_viewport`** becomes the complement: the viewport's rect, minus
+  what the network holds, minus the floating panes, minus the second
+  editor's rect. Until 2026-10-06 it was bounded by the old column split
+  (`splitter2_x`) instead, so the scene under the params HUD's rows, right of
+  it, could be neither orbited nor right-clicked.
+- **A hidden network has no area** (`in_network_area`), so the middle
+  button over the scene pans the camera then; shown, it is the graph's
+  everywhere.
+
+What changes for the user: a plain click on empty space is not the network's
+— it ORBITS THE CAMERA instead (see below), which is what makes the overlay
+feel like a scene with a graph on it rather than a graph with a picture
+behind it. Deselecting is on Escape.
 
 ### The params plate fits its rows, and is optional (since 2026-10-06)
 
@@ -2885,9 +2907,9 @@ bottom raised by a spreadsheet tucked under it, tabbable and movable.
   the HUD. The legacy column branches (circular network, detached circular
   window) still place it in their right column.
 - **Under every plate.** Draw order: viewport (-7), the HUD (-6), the
-  network plates (-5), then the rest. `plates_over_params` is the plates
-  over it (the network's while it has one, the second editor's, the
-  spreadsheet, the playbar, stubs included); `params_claims` takes them
+  network panels (-5, drawn with no plate), then the rest.
+  `plates_over_params` is the plates over it (the spreadsheet, the
+  playbar, stubs included; the network editors have none); `params_claims` takes them
   out, so a press, the wheel, a row's right-click (`param_row_at`) and
   `plate_at` there are the plate's. Text is the hard part: the engine lays
   ALL text out after all geometry, so a HUD label under a plate would be
@@ -2921,24 +2943,6 @@ are more immediate than a selection. There is also a `deselect` command, shipped
 UNBOUND so it is findable in the palette — deliberately not Ctrl+D, which the
 plugin uses for deselect-all but which this app already gives to Circular Pane.
 
-**Off is the default** (since 2026-10-06; `default_network_plate`, and
-what a state.kdl or a project display block without the key reads). A
-project saved with the plate on still opens with it on, its display block
-saying so. The suite's tests written against the plated layout — the dock
-geometry, the grid drags, a pointer at the viewport's middle — pin
-`state.network_plate = true` after `State::new`, which keeps what they
-check what it was.
-
-`ViewportSettings::network_plate` persists it, beside the viewport toggles
-rather than in the project's pane-state list: a pane's VISIBILITY belongs to
-the project, but whether its surface is drawn is how you like to work, and it
-should outlive any one file. It is reachable three ways that cannot disagree,
-because all three run one `Action::ToggleNetworkPlate` — the View settings
-node's Network > Plate row, the network pane's View menu ("Network Plate"), and
-the `toggle_network_plate` command. The action marks `settings_changed` and
-lets `execute_action` save once at its end, like every other viewport toggle,
-rather than writing the file itself.
-
 ### The network editor's right-click menu
 
 A right press on EMPTY graph space opens the network's own context menu; a press
@@ -2970,7 +2974,8 @@ other rows below swapped the plate on a click — some with a Back row, most
 with no way back — two gestures for one idea.
 
 The page rows: the viewport menu's **Style** and **Markers** (pages of rows;
-and **Network**, the network menu, while the network has no plate;
+and **Network**, the network menu, while the network overlays the scene
+(not circular);
 its **Attribute Visualizers** row was one too, into the dialog, until
 2026-10-06 — it is a plain row now, opening them in the params HUD); the
 network menu's **Add Node**
@@ -3039,10 +3044,10 @@ palette used to be reachable only from Tab's inline handler. It ships UNBOUND,
 like `deselect`: Tab already opens it from the event loop, and a default chord
 here would duplicate a key the loop claims.
 
-With the plate OFF the press never gets here — `in_network_pane` narrows to the
-nodes in overlay mode, so empty space is the scene's and opens the VIEWPORT
+Outside the circular pane the press never gets here — `in_network_pane` narrows
+to the nodes in overlay mode, so empty space is the scene's and opens the VIEWPORT
 menu. That is the overlay's whole rule, and it predates this menu. So in
-overlay mode (the default since 2026-10-06) **this menu is a page of the
+overlay mode (always, outside the circular pane, since 2026-10-06) **this menu is a page of the
 viewport's**: a **Network** row heads the viewport menu
 (`ViewportMenuAction::NetworkPage`) and turns it into this one under a
 `‹ Viewport` band (`State::network_menu_from`). The viewport menu's press
@@ -3193,8 +3198,9 @@ test for the row.
 
 ### The cursor is a region, and dragging the grid grows it
 
-A left press on EMPTY grid puts the cursor on the pressed cell — on the press,
-not the release — and arms an expansion drag from it. Dragging grows the cursor
+A left press on EMPTY grid — with ctrl held, outside the circular pane, where
+a plain press on empty space orbits the camera (see "The network has no
+plate") — puts the cursor on the pressed cell — on the press, not the release — and arms an expansion drag from it. Dragging grows the cursor
 from that anchor to the cell under the pointer. `State::grid_cursor_region` is the one derivation,
 `(col, row, cols, rows)`, never smaller than one cell; `grid_cursor_rect` is the
 window-space union the outline is painted on, which for the usual one-cell
@@ -3938,7 +3944,8 @@ gestures, `pan_drag` beside `orbit_drag`:
 
 - **the middle button, dragged** over the scene. Armed after the
   network's own pan, which the middle button is wherever the network is
-  laid out — with its plate off that is the whole window — and AHEAD of
+  laid out — the whole window, the network having no plate, so the
+  camera has it only while the network is hidden or circular — and AHEAD of
   the press cascade's `button != Left && != Right` return, which is where
   the first cut put it and where it never ran;
 - **shift and the left button**, in the orbit's own arm, so a viewer
diff --git a/src/app.rs b/src/app.rs
index 581f494..6c1e610 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -634,7 +634,6 @@ pub const NETWORK_MENU_COMMANDS: &[Option<&'static str>] = &[
     Some("frame_cursor"),
     None,
     Some("reset_zoom"),
-    Some("toggle_network_plate"),
     Some("toggle_circular_pane"),
 ];
 
@@ -2222,13 +2221,6 @@ pub struct ViewportSettings {
     pub grid_thickness: f32,
     #[serde(default = "default_grid_color")]
     pub grid_color: [f32; 3],
-    /// Whether the network pane draws its plate. Lives beside the viewport
-    /// settings rather than in a pane-state list because it is an appearance
-    /// choice that outlives any one project — a pane's VISIBILITY belongs to
-    /// the project, but whether its surface is drawn is how you like to work.
-    /// Off by default (since 2026-10-06): the nodes stand on the scene.
-    #[serde(default = "default_network_plate")]
-    pub network_plate: bool,
     /// Whether the params HUD draws its plate — one FITTED to its rows,
     /// not to the HUD's rect. On by default; off, the rows stand on the
     /// scene. Either way the HUD claims only the band its rows cover.
@@ -2450,10 +2442,6 @@ impl Default for RenderSettings {
     }
 }
 
-fn default_network_plate() -> bool {
-    false
-}
-
 fn default_params_plate() -> bool {
     true
 }
@@ -2467,7 +2455,6 @@ impl Default for ViewportSettings {
             camera_pivot_size: 1.0,
             show_grid_enabled: true,
             show_origin_enabled: true,
-            network_plate: false,
             params_plate: true,
             node_wire_style: String::new(),
             show_point_markers: false,
@@ -2685,9 +2672,9 @@ fn hex_to_float_array4(hex: &str) -> Option<[f32; 4]> {
 /// terminal's) cannot read each other's writes. And per TEST within a
 /// process, because settings are shared mutable state and libtest runs tests
 /// in parallel threads: `the_suite_does_not_write_the_users_own_settings`
-/// runs the plate toggle, which SAVES `network_plate = false`, and with one
+/// ran the network plate toggle (retired since), which SAVED `network_plate = false`, and with one
 /// file between them every `State::new` racing it loaded that and came up
-/// with the plate switched off — `test_the_network_plate_is_an_option`
+/// with the plate switched off — `test_the_network_plate_is_an_option` (retired with it)
 /// failing perhaps one run in six, in an assertion about a row in the View
 /// node. Nothing in the suite had ever toggled a setting before
 /// 2026-09-23, so this was not a pre-existing race so much as one that
@@ -2728,7 +2715,7 @@ impl DesignSettings {
     /// `State::new` loads the BUNDLED project, whose meta subnets overwrote
     /// the live viewport flags on every parameter change (the meta node is
     /// retired, but the hazard was real and the redirect is what caught it).
-    /// Any test that then reached `save_settings` — `toggle_network_plate`,
+    /// Any test that then reached `save_settings` — a plate toggle,
     /// the dialog's toggle rows — wrote the bundled project's
     /// show_grid / show_cube / show_origin over the user's own state.kdl.
     /// (The cube guide has since been removed.)
@@ -3100,8 +3087,8 @@ pub struct State {
     /// with the flag saying the open menu is this one.
     pub network_menu_active: bool,
     pub network_menu_actions: Vec<NetworkMenuAction>,
-    /// The menu the network menu was turned to from — the viewport's, with
-    /// the plate off — which its back band returns to. None when a right
+    /// The menu the network menu was turned to from — the viewport's, while
+    /// the network overlays the scene — which its back band returns to. None when a right
     /// press on the graph opened it.
     pub network_menu_from: Option<crate::menu_page::MenuOrigin>,
     /// The grid cell under the viewport menu's right press, while the
@@ -3274,12 +3261,6 @@ pub struct State {
     pub space_pressed: bool,
     pub active_camera: String,
     pub show_network: bool,
-    /// Whether the network pane draws its PLATE — the filled, frosted surface
-    /// the graph sits on. With it off the nodes and wires overlay the 3D scene
-    /// directly, since the viewport is full-bleed and the network floats over
-    /// it. The pane is still there: it keeps its rect, its focus, its corner
-    /// menus and its clip; only the surface under it stops being drawn.
-    pub network_plate: bool,
     /// Whether the params pane draws its PLATE. With it off the rows stand
     /// directly over the scene, and the pane claims only what its rows
     /// cover (`params_claim`), so the scene under the rest of its rect
@@ -3784,7 +3765,6 @@ impl State {
                 origin_size: self.origin_size,
                 grid_thickness: self.grid_thickness,
                 grid_color: self.viewport().grid_color,
-                network_plate: self.network_plate,
                 params_plate: self.params_plate,
                 node_wire_style: self.slots.content.inner().chosen_wire_style().map(|w| w.name().to_string()).unwrap_or_default(),
                 show_point_markers: self.show_point_markers,
@@ -3866,7 +3846,6 @@ impl State {
         self.camera_pivot_size = v.camera_pivot_size;
         self.origin_size = v.origin_size;
         self.grid_thickness = v.grid_thickness;
-        self.network_plate = v.network_plate;
         self.params_plate = v.params_plate;
         self.set_node_wire_style(cce_ui::widget::display::WireStyle::parse(&v.node_wire_style));
         self.circular_network_pane = self.is_detached_network || v.circular_pane;
@@ -4275,14 +4254,14 @@ impl State {
         self.viewport_dirty = true;
     }
 
-    /// Whether the network is drawn as an OVERLAY on the scene rather than on
-    /// its own plate: the plate switched off, in the ordinary docked layout.
+    /// Whether the network is drawn as an OVERLAY on the scene: shown, in the
+    /// ordinary layout. It has no plate (since 2026-10-06; it was a switch),
+    /// so its nodes stand on the scene and it spans the window.
     ///
     /// The circular pane and a detached network window have their own
     /// geometry and their own hit tests, and neither is a thing to overlay.
     pub fn network_overlay(&self) -> bool {
-        !self.network_plate
-            && self.show_network
+        self.show_network
             && !self.circular_network_pane
             && !self.is_detached_network
     }
@@ -4406,11 +4385,7 @@ impl State {
         if self.circular_network_pane {
             return self.circular_network_layout.hit_test_content(px, py, 0.0, breadcrumb_h());
         }
-        self.network_plate
-            && [NETWORK_PANEL_IDX, crate::slots::NETWORK_PANEL2_IDX].iter().any(|&idx| {
-                let (x, y, w, h) = self.positions[idx];
-                w > 0.0 && h > 0.0 && px >= x && px < x + w && py >= y && py < y + h
-            })
+        false
     }
 
     fn over_floating_pane(&self) -> bool {
@@ -4450,12 +4425,17 @@ impl State {
     ///
     /// Distinct from [`in_network_pane`](Self::in_network_pane), which in
     /// overlay mode narrows to the nodes so a click can reach the scene. The
-    /// two differ only when the plate is off, and the difference is the point:
+    /// two differ only in overlay mode, and the difference is the point:
     /// a CLICK on empty space is not the network's, but a PAN gesture over
     /// that same space is — middle-drag and space+left mean nothing to the
     /// scene, and a graph you cannot pan by dragging because its own surface
     /// stopped being drawn would be a strange thing to ship.
     pub fn in_network_area(&self, px: f32, py: f32) -> bool {
+        // A hidden network has no area: its rect is still laid out, and a
+        // middle-drag panned the graph no one could see.
+        if !self.show_network {
+            return false;
+        }
         if self.circular_network_pane {
             return self.circular_network_layout.hit_test_content(px, py, 0.0, breadcrumb_h());
         }
@@ -4612,14 +4592,19 @@ impl State {
     }
 
     pub fn cursor_in_viewport(&self) -> bool {
+        let (px, py) = (self.cursor_x, self.cursor_y);
+        let inside = |(x, y, w, h): (f32, f32, f32, f32)| w > 0.0 && h > 0.0 && px >= x && px < x + w && py >= y && py < y + h;
         if self.network_overlay() {
-            // The complement of the overlay: everything in the body the
-            // network is not holding and no floating pane covers.
-            return !self.in_network_pane()
+            // The complement of the overlay: everything in the viewport the
+            // network is not holding and no floating pane covers — the
+            // second editor, docked, holds its rect. Bounded by the
+            // viewport's rect; until 2026-10-06 it stopped at the old
+            // column split (`splitter2_x`), so the scene under the params
+            // HUD's rows, right of it, was nobody's.
+            return inside(self.positions[VIEWPORT_IDX])
+                && !self.in_network_pane()
                 && !self.over_floating_pane()
-                && self.cursor_x < self.splitter_layout.splitter2_x
-                && self.cursor_y >= HEADER_H
-                && self.cursor_y < self.height - STATUS_H;
+                && !inside(self.positions[crate::slots::NETWORK_PANEL2_IDX]);
         }
         // Minus the floating panes here too. The spreadsheet and the playbar
         // sit INSIDE the centre column, over the full-bleed scene, and this
@@ -4638,8 +4623,6 @@ impl State {
         // and a right-click there opened nothing. The circular pane is
         // excluded by its callers (`in_circle_network_pane`): its rect is
         // the circle's bounding box, whose corners are scene.
-        let (px, py) = (self.cursor_x, self.cursor_y);
-        let inside = |(x, y, w, h): (f32, f32, f32, f32)| w > 0.0 && h > 0.0 && px >= x && px < x + w && py >= y && py < y + h;
         let over_network = !self.circular_network_pane
             && [NETWORK_PANEL_IDX, crate::slots::NETWORK_PANEL2_IDX].iter().any(|&idx| inside(self.positions[idx]));
         inside(self.positions[VIEWPORT_IDX]) && !over_network && !self.over_floating_pane()
@@ -4652,7 +4635,8 @@ impl State {
     /// The floating network pane's edge-resize hotspot at (cx, cy) — only the right
     /// edge resizes. `None` while the pane is circular or hidden.
     pub fn network_resize_edge_at(&self, cx: f32, cy: f32) -> Option<ResizeDirection> {
-        if self.circular_network_pane || !self.show_network {
+        // The overlay spans the window and has no edge to drag.
+        if self.circular_network_pane || !self.show_network || self.network_overlay() {
             return None;
         }
         let (fx, fy, _, fh) = self.floating_network_layout;
@@ -4737,10 +4721,6 @@ impl State {
                 out.push(r);
             }
         };
-        if self.network_plate && !self.circular_network_pane {
-            take(NETWORK_PANEL_IDX);
-            take(crate::slots::NETWORK_PANEL2_IDX);
-        }
         take(SPREADSHEET_IDX);
         take(PLAYBAR_IDX);
         if self.circular_network_pane && self.show_network && !self.detached_circular_network {
@@ -5594,11 +5574,6 @@ impl State {
             "Detach Circular Window" | "Detach Pane" => {
                 self.execute_action(Action::DetachCircularWindow);
             }
-            // Both spellings reach the one action: "Show Network Plate" is
-            // the View node's parameter name, "Network Plate" the menu row.
-            "Show Network Plate" | "Network Plate" => {
-                self.execute_action(Action::ToggleNetworkPlate);
-            }
             "Show Network Pane" => {
                 self.show_network = !self.show_network;
                 self.slots.content.set_visible(self.show_network);
@@ -7376,8 +7351,8 @@ impl State {
             None => {}
         }
 
-        // With the network's plate off, empty graph space is the scene's,
-        // so the network's menu is a page of this one, at its head.
+        // While the network overlays the scene, empty graph space is the
+        // scene's, so the network's menu is a page of this one, at its head.
         if self.network_overlay() {
             row(&mut options, &mut actions, "Network".into(), ViewportMenuAction::NetworkPage);
             row(&mut options, &mut actions, "-".into(), sep);
@@ -8602,7 +8577,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
             splitter2: Splitter::new(SPLITTER_W),
             param: ParametersBg::new(),
             canvas: Canvas::new(),
-            left_menubar: MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Network Plate", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0),
+            left_menubar: MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0),
             right_menubar: MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["square_aspect"]).with_item("Guides", &["Show Grid", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 1),
             param_menubar: MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2),
             status: StatusBar::new().with_text("Ready"),
@@ -8820,7 +8795,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
             space_pressed: false,
             active_camera,
             show_network: true,
-            network_plate: settings.viewport.network_plate,
             params_plate: settings.viewport.params_plate,
             show_viewport: true,
             show_parameters: true,
@@ -9769,9 +9743,9 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                 };
 
                 let (px, py, pw, ph) = rect_for(NETWORK_PANEL_IDX, self);
-                // With the plate off the network is an overlay on the scene,
-                // so it takes the whole body instead of its dock: there is no
-                // surface left to bound it, and a graph confined to a
+                // The network has no plate (since 2026-10-06): it is an
+                // overlay on the scene, so it takes the whole body instead of
+                // its dock — there is no surface to bound it, and a graph confined to a
                 // rectangle you cannot see is worse than one that spans what
                 // it is drawn over. Everything below derives from these four
                 // numbers — content, panel, breadcrumb — so overriding them
@@ -10590,19 +10564,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
             Action::NetworkPan(dc, dr) => {
                 self.network_pan_view(dc, dr);
             }
-            Action::ToggleNetworkPlate => {
-                self.network_plate = !self.network_plate;
-                self.rebuild_positions();
-                self.apply_layout();
-                self.update_status_text(if self.network_plate {
-                    "Network plate on."
-                } else {
-                    "Network plate off — the graph overlays the scene."
-                });
-                // Saved through the same `settings_changed` path every other
-                // viewport toggle uses, rather than a save call of its own.
-                settings_changed = true;
-            }
             Action::ToggleParamsPlate => {
                 self.params_plate = !self.params_plate;
                 self.update_status_text(if self.params_plate {
@@ -11576,7 +11537,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                 }
                 let in_network_pane = self.in_network_pane();
 
-                // Panning asks the AREA, not the nodes: with the plate off a
+                // Panning asks the AREA, not the nodes: in overlay mode a
                 // middle-drag over empty space still pans the graph, because
                 // nothing else wants that gesture.
                 let is_pan_trigger = self.in_network_area(self.cursor_x, self.cursor_y)
@@ -11617,8 +11578,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
 
                 // A middle press on the scene slides the camera. After the
                 // network's own pan above, which the middle button is
-                // wherever the network is laid out — with its plate off,
-                // the whole window, where shift and the left button pan
+                // wherever the network is laid out — in overlay mode the
+                // whole window, where shift and the left button pan
                 // the camera instead.
                 if *button == MouseButton::Middle
                     && *btn_state == ElementState::Pressed
@@ -11939,6 +11900,34 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                             }
                         }
 
+                        // Ctrl and a left press on empty space over the
+                        // overlaid network box-selects: the press is the
+                        // empty-grid press of the network's own cascade — the
+                        // cursor to the cell pressed, an expansion drag armed
+                        // from it, settled on the release — which a plain
+                        // press there cannot be, empty space being the
+                        // scene's to orbit. The network takes focus, as a
+                        // press on its grid gives it.
+                        if *button == MouseButton::Left
+                            && self.modifiers.control_key()
+                            && self.network_overlay()
+                            && !in_network_pane
+                            && self.in_network_area(self.cursor_x, self.cursor_y)
+                            && self.app_drag.is_none()
+                        {
+                            let (col, row) = self.cell_at(self.cursor_x, self.cursor_y);
+                            self.grid_cursor_col = col;
+                            self.grid_cursor_row = row;
+                            self.grid_cursor_expanse = None;
+                            self.grid_cursor_drag = Some((col, row));
+                            self.focused_pane = LEFT_MENUBAR_IDX;
+                            if let Some(old) = self.focused_widget.take() {
+                                self.slots.get_dyn_mut(old).unfocus();
+                            }
+                            self.sync_pane_focus();
+                            return true;
+                        }
+
                         // A left press on empty scene arms a camera orbit.
                         // AFTER the viewer state above, so dragging a handle
                         // still edits it, and after the node hit tests, so a
diff --git a/src/command.rs b/src/command.rs
index 973652c..9b3f88c 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -191,7 +191,6 @@ pub const COMMANDS: &[Command] = &[
     Command { id: "zoom_in", label: "Zoom In", context: Context::Network, run: Run::Menu("Zoom In"), default_chord: None },
     Command { id: "zoom_out", label: "Zoom Out", context: Context::Network, run: Run::Menu("Zoom Out"), default_chord: None },
     Command { id: "reset_zoom", label: "Reset Zoom", context: Context::Network, run: Run::Menu("Reset Zoom"), default_chord: None },
-    Command { id: "toggle_network_plate", label: "Network Plate", context: Context::Network, run: Run::Key(Action::ToggleNetworkPlate), default_chord: Some("Shift+p") },
     Command { id: "toggle_params_plate", label: "Parameters Plate", context: Context::Parameters, run: Run::Key(Action::ToggleParamsPlate), default_chord: None },
     Command { id: "toggle_circular_pane", label: "Circular Pane", context: Context::Network, run: Run::Key(Action::ToggleCircularPane), default_chord: Some("Ctrl+d") },
     Command { id: "detach_circular_window", label: "Detach Circular Window", context: Context::Network, run: Run::Key(Action::DetachCircularWindow), default_chord: None },
diff --git a/src/dialog.rs b/src/dialog.rs
index 7f9fd24..5be0176 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -2155,7 +2155,6 @@ impl State {
             "toggle_wire_single_color" => self.wire_single_color,
             "toggle_ray_traced_preview" => self.viewport().rt_mode,
             "toggle_square_viewport" => self.square_viewport,
-            "toggle_network_plate" => self.network_plate,
             "toggle_params_plate" => self.params_plate,
             "toggle_circular_pane" => self.circular_network_pane,
             "detach_circular_window" => self.detached_circular_network,
diff --git a/src/main.rs b/src/main.rs
index 921f2ff..b9fe369 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -407,7 +407,6 @@ mod tests {
         use crate::slots::{NETWORK_PANEL_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
         use cce_ui::widget::{context_menu, MouseButton};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.show_spreadsheet = true;
         state.show_playbar = true;
@@ -444,12 +443,17 @@ mod tests {
         press_at(&mut state, 2.0, 2.0, MouseButton::Left);
         state.params_plate = false;
 
-        // The network: its own rows, then one Plate row, a page: the menu
-        // turns into the plate's rows, under a band back to it.
+        // The network: its menu is the viewport menu's Network page (empty
+        // graph space is the scene's), and in it its own rows, then one
+        // Plate row, a page: the menu turns into the plate's rows, under a
+        // band back to it.
         let (cx, cy, cw, ch) = state.positions[crate::slots::CONTENT_IDX];
-        let (px, py) = (cx + cw * 0.85, cy + ch * 0.2);
-        assert!(state.graph().node_at(px, py).is_none());
+        let (px, py) = (cx + cw * 0.3, cy + ch * 0.6);
+        assert!(state.graph().node_at(px, py).is_none() && !state.over_floating_pane_at(px, py));
         press_at(&mut state, px, py, MouseButton::Right);
+        assert!(state.viewport_menu_open());
+        press_at(&mut state, context_menu::x() + 8.0, context_menu::row_y(0) + 4.0, MouseButton::Left);
+        assert!(state.network_menu_active, "the Network row turned the menu into the network's");
         let options = cce_ui::widget::context_menu::options();
         assert_eq!(options.first().map(String::as_str), Some("Add Node"));
         assert!(!options.contains(&collapse) && !options.iter().any(|o| o.starts_with("Move To")), "no plate rows inline: {options:?}");
@@ -593,58 +597,14 @@ mod tests {
         assert!(parent.slots.get_dyn(VIEWPORT_IDX).visible(), "the rest of the parent survived");
     }
 
-    /// Dock swap: Move To another dock swaps occupants,
-    /// and the dock-owned dimensions stay put — the network lands in the
-    /// bottom strip's rect, the spreadsheet in the left column's.
-    /// A left press on the spreadsheet's column header reaches the widget,
-    /// which sorts the column — it does not orbit the camera.
-    ///
-    /// `cursor_in_viewport` used to be the whole centre column, spreadsheet
-    /// included, and the orbit arm at the top of the left-press path returns
-    /// before any widget is asked. So the toolkit's header-click sort
-    /// (tested in cce-ui) never fired in this app: clicking a header
-    /// hovered it, tinted it, and did nothing.
-    /// The scene is everything in the viewport's rect that no plate covers
-    /// — right up to the network plate's edge. The test used to carve out
-    /// the old COLUMN layout, so the band between the floating plate's right
-    /// edge and `splitter1_x + SPLITTER_W`, and the strip above the network
-    /// content, were nobody's: a right-click there opened no menu.
-    #[test]
-    fn the_scene_starts_at_the_network_plates_edge() {
-        use crate::slots::{NETWORK_PANEL_IDX, VIEWPORT_IDX};
-        use crate::window::{LocalPosition, WindowEvent};
-        use cce_ui::widget::{ElementState, MouseButton};
-        let mut state = State::new(false);
-        state.resize(1600.0, 900.0, 1.0);
-        state.network_plate = true;
-        state.splitter_layout.splitter1_x = 700.0;
-        state.rebuild_positions();
-        state.apply_layout();
-        let (nx, ny, nw, nh) = state.positions[NETWORK_PANEL_IDX];
-        assert!(nw > 0.0 && nx + nw < 690.0, "the plate ends short of the old column split");
-        let at = |state: &mut State, x: f32, y: f32| {
-            state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
-            state.cursor_in_viewport()
-        };
-        assert!(!at(&mut state, nx + nw - 4.0, ny + nh * 0.5), "the plate is the network's");
-        assert!(at(&mut state, nx + nw + 4.0, ny + nh * 0.5), "just right of the plate is scene");
-        let (vx, vy, vw, _) = state.positions[VIEWPORT_IDX];
-        assert!(at(&mut state, vx + vw * 0.5, vy + 4.0), "the strip along the top is scene");
-
-        // And the right press there opens the viewport's menu.
-        at(&mut state, nx + nw + 4.0, ny + nh * 0.5);
-        state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Right });
-        assert!(state.viewport_menu_open(), "a right-click beside the plate opens the viewport menu");
-    }
-
-    /// With the network's plate off (the default) a right press on empty
+    /// The network has no plate, so a right press on empty
     /// graph space is the scene's and opens the VIEWPORT menu, so the
     /// network's own menu is a page of it: a Network row at its head that
     /// turns into the network menu under a band back to the viewport's.
     /// Turning there focuses the network and puts the grid cursor on the
     /// cell the menu was opened over, where Add Node will place; a menu
-    /// opened over no network area moves no cursor. With the plate on there
-    /// is no such row: the network's empty space opens its menu itself.
+    /// opened over no network area moves no cursor. In the circular pane
+    /// there is no such row: its empty space opens the network's menu itself.
     #[test]
     fn the_network_menu_is_a_page_of_the_viewport_menu_without_the_plate() {
         use crate::app::{NetworkMenuAction, ViewportMenuAction as A};
@@ -657,7 +617,10 @@ mod tests {
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
-        assert!(state.network_overlay(), "the plate is off by default");
+        assert!(state.network_overlay(), "the network overlays the scene");
+        // Spanning the window, it has no edge to drag where its dock's was.
+        let (fx, fy, _, fh) = state.floating_network_layout;
+        assert!(state.network_resize_edge_at(fx + state.left_dock_width(), fy + fh * 0.5).is_none());
         let press = |state: &mut State, x: f32, y: f32, b: MouseButton| {
             state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
             state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: b });
@@ -703,10 +666,8 @@ mod tests {
         assert_eq!(state.open_menu_origin(), Some(MenuOrigin::Viewport));
         state.close_viewport_menu();
 
-        // With the plate on, the viewport menu has no Network row.
-        state.network_plate = true;
-        state.rebuild_positions();
-        state.apply_layout();
+        // The circular pane takes its own empty space: no Network row.
+        state.circular_network_pane = true;
         assert!(!state.viewport_menu_rows_of(None).1.contains(&A::NetworkPage));
     }
 
@@ -725,7 +686,7 @@ mod tests {
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
-        assert!(state.network_overlay(), "the plate is off by default");
+        assert!(state.network_overlay(), "the network overlays the scene");
         state.set_active_camera("Default Camera");
         let at = |state: &mut State, x: f32, y: f32| {
             state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
@@ -777,6 +738,28 @@ mod tests {
         assert!(!state.overlay_wheel_to_graph(ScrollPhase::Finger), "a new gesture over empty space is the scene's");
     }
 
+    /// The network plate is retired; a settings file or a project display
+    /// block from when it was a switch still says `network_plate`, and must
+    /// still load — the key is ignored, not an error that would read the
+    /// whole block as the defaults.
+    #[test]
+    fn a_retired_network_plate_key_still_loads() {
+        let state = State::new(false);
+        let mut json = serde_json::to_value(state.display_settings()).unwrap();
+        json["viewport"]["network_plate"] = serde_json::json!(true);
+        json["viewport"]["params_plate"] = serde_json::json!(false);
+        let d: crate::app::DisplaySettings = serde_json::from_value(json).expect("a display block naming network_plate loads");
+        assert!(!d.viewport.params_plate, "and the rest of it is read");
+    }
+
+    /// A left press on the spreadsheet's column header reaches the widget,
+    /// which sorts the column — it does not orbit the camera.
+    ///
+    /// `cursor_in_viewport` used to be the whole centre column, spreadsheet
+    /// included, and the orbit arm at the top of the left-press path returns
+    /// before any widget is asked. So the toolkit's header-click sort
+    /// (tested in cce-ui) never fired in this app: clicking a header
+    /// hovered it, tinted it, and did nothing.
     #[test]
     fn spreadsheet_header_press_reaches_the_widget_not_the_camera() {
         use crate::slots::{SPREADSHEET_IDX, SPREADSHEET_MENUBAR_IDX, VIEWPORT_IDX};
@@ -1098,14 +1081,19 @@ mod tests {
         assert!(s.points().has("uv") && !s.points().has("UV"));
     }
 
+    /// Dock swap: Move To another dock swaps occupants, and the dock-owned
+    /// dimensions stay put — the second network editor lands in the bottom
+    /// strip's rect, the spreadsheet in the right dock's.
     #[test]
     fn test_dock_swap_repositions_plates() {
         use crate::app::Dock;
-        use crate::slots::{NETWORK_PANEL_IDX, SPREADSHEET_IDX};
+        use crate::slots::{NETWORK_PANEL2_IDX as NETWORK_PANEL_IDX, SPREADSHEET_IDX};
+        // The second network editor: the first spans the window, whatever
+        // dock it is in, since it has no plate.
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.show_spreadsheet = true;
+        state.add_dock_tab(Dock::Right, NETWORK_PANEL_IDX);
         state.rebuild_positions();
         state.apply_layout();
 
@@ -1116,7 +1104,7 @@ mod tests {
 
         state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Bottom);
         assert_eq!(state.dock_of_pane(NETWORK_PANEL_IDX), Some(Dock::Bottom));
-        assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Left));
+        assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Right));
 
         let net_after = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
         let ss_after = state.slots.get_dyn(SPREADSHEET_IDX).rect();
@@ -1128,7 +1116,7 @@ mod tests {
         assert!(net_after.2 > 0.0 && ss_after.2 > 0.0, "a pane vanished in the swap");
 
         // Dropping it back restores the original arrangement.
-        state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Left);
+        state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Right);
         assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Bottom));
     }
 
@@ -2254,9 +2242,8 @@ mod tests {
     #[test]
     fn the_playbar_is_attached_to_the_bottom_edge() {
         use crate::app::{playbar_shelf_h, PLAYBAR_H, STATUS_H};
-        use crate::slots::{NETWORK_PANEL_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
+        use crate::slots::{PLAYBAR_IDX, SPREADSHEET_IDX};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.execute_menu_action("Show Playbar Pane");
         state.execute_menu_action("Show Spreadsheet Pane");
@@ -2276,7 +2263,8 @@ mod tests {
         let prev = state.slots.playbar.inner().transport_button_rect(pb_rect, -1).expect("the step buttons are on");
         assert!(prev.x >= lip, "clear of the left lip: {prev:?}");
         assert!(prev.y + prev.height <= 900.0 - STATUS_H - lip, "clear of the bottom lip: {prev:?}");
-        for idx in [NETWORK_PANEL_IDX, SPREADSHEET_IDX] {
+        // (The network has no plate: it spans the window, under the shelf.)
+        for idx in [SPREADSHEET_IDX] {
             let (_, y, _, h) = state.positions[idx];
             assert_eq!(y + h, pb.1 - 18.0, "the {idx} plate stops a gap above it");
         }
@@ -2296,7 +2284,6 @@ mod tests {
         use crate::app::{Dock, NO_PANE};
         use crate::slots::{NETWORK_PANEL2_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -2354,13 +2341,21 @@ mod tests {
         assert!(pb.scrollbar_visible(), "the sphere's rows overflow a {hh} px HUD and scroll");
         assert_eq!(state.params_claim(), (hx, hy, hw, hh), "the plate fills the HUD");
 
-        // Under the plate in the right dock: it takes the pointer there.
-        assert!(state.plates_over_params().contains(&state.positions[NETWORK_PANEL2_IDX]));
-        let (nx, ny, nw, _) = state.positions[NETWORK_PANEL2_IDX];
+        // A network editor in the right dock has no plate, so it covers
+        // nothing of the HUD.
+        assert!(!state.plates_over_params().contains(&state.positions[NETWORK_PANEL2_IDX]));
+        // Under a plate in the right dock: it takes the pointer there.
+        state.set_spreadsheet_full_width(false);
+        state.move_pane_to_dock(SPREADSHEET_IDX, Dock::Right);
+        state.rebuild_positions();
+        state.apply_layout();
+        let (hx, hy, hw, _) = state.positions[PARAM_IDX];
+        assert!(state.plates_over_params().contains(&state.positions[SPREADSHEET_IDX]));
+        let (nx, ny, nw, _) = state.positions[SPREADSHEET_IDX];
         let over = (nx.max(hx) + 5.0, ny.max(hy) + 30.0);
         assert!(over.0 < nx + nw && over.0 < hx + hw);
         assert!(!state.params_claims(over.0, over.1), "a plate over the HUD takes the pointer");
-        assert_eq!(state.plate_at(over.0, over.1), Some(NETWORK_PANEL2_IDX));
+        assert_eq!(state.plate_at(over.0, over.1), Some(SPREADSHEET_IDX));
 
         // An older save docked the params pane; it loads out of the dock.
         let dir = std::env::temp_dir().join(format!("cce-designer-hud-dock-test-{}", std::process::id()));
@@ -2420,7 +2415,6 @@ mod tests {
     #[test]
     fn the_params_plate_fits_its_rows() {
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         assert!(state.params_plate, "the plate is on by default");
         assert_eq!(state.command_toggle_state("toggle_params_plate"), Some(true));
         // A HUD with no rows collapses its plate to the small circle, and
@@ -2876,7 +2870,6 @@ mod tests {
     fn the_viewport_menu_groups_its_display_rows() {
         use crate::app::{ViewportMenuAction as A, ViewportMenuPage as P};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         let groups = |page: Option<P>| -> Vec<Vec<A>> {
             let (options, actions) = state.viewport_menu_rows_of(page);
             assert_eq!(options.len(), actions.len());
@@ -2886,6 +2879,7 @@ mod tests {
         assert_eq!(
             groups(None),
             vec![
+                vec![A::NetworkPage],
                 vec![A::FrameAll, A::OneToOne],
                 vec![A::Command("toggle_grid"), A::Command("toggle_origin"), A::Command("toggle_camera_pivot"), A::CameraPivotSizeSlider],
                 vec![A::Page(P::Style), A::Page(P::Markers), A::Command("attribute_visualizers")],
@@ -3490,7 +3484,9 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{context_menu, ElementState, MouseButton, MouseScrollDelta};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
+        // Without the network the menu has no Network row at its head, so
+        // its rows are the ones counted below.
+        state.show_network = false;
         state.cursor_x = 300.0;
         state.cursor_y = 200.0;
         state.open_viewport_context_menu();
@@ -3932,7 +3928,6 @@ mod tests {
         let _ = fs::remove_dir_all(&dir);
 
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -3943,7 +3938,6 @@ mod tests {
         state.set_active_camera("Default Camera");
         state.save_to_file(&dir).expect("save");
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -3966,13 +3960,11 @@ mod tests {
 
         // New: back to the Default Camera from a node, on both copies.
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.new_project();
         assert_eq!(state.viewport().active_camera, "Default Camera");
 
         // The viewport menu's choice: a node, then the default again.
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.set_active_camera("Default Camera");
         state.set_active_camera("camera1");
         assert_eq!(state.viewport().active_camera, "camera1");
@@ -4033,7 +4025,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{ElementState, MouseButton, MouseScrollDelta, Position};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -4096,7 +4087,21 @@ mod tests {
         assert!(state.cursor_in_viewport());
         let orbit = |state: &State| (state.viewport().rotation_x, state.viewport().rotation_y);
 
+        // The network lies over the whole window, and the middle button is
+        // its pan wherever it is laid out: the graph slides, the camera not.
         let (pivot, turned) = (state.viewport().pivot, orbit(&state));
+        let graph = (state.pan_x, state.pan_y);
+        press(&mut state, MouseButton::Middle, ElementState::Pressed);
+        assert!(state.pan_drag.is_none(), "over the network the middle button is the graph's");
+        at(&mut state, cx + 40.0, cy + 10.0);
+        press(&mut state, MouseButton::Middle, ElementState::Released);
+        assert_ne!((state.pan_x, state.pan_y), graph, "the graph panned");
+        assert_eq!(state.viewport().pivot, pivot);
+
+        // With the network hidden it is the camera's.
+        state.execute_menu_action("Show Network Pane");
+        assert!(!state.show_network);
+        at(&mut state, cx, cy);
         press(&mut state, MouseButton::Middle, ElementState::Pressed);
         assert!(state.pan_drag.is_some() && state.pointer_captured(), "a middle press arms the pan");
         at(&mut state, cx + 40.0, cy + 10.0);
@@ -4104,6 +4109,8 @@ mod tests {
         assert!(state.pan_drag.is_none());
         assert_ne!(state.viewport().pivot, pivot, "the middle drag panned");
         assert_eq!(orbit(&state), turned, "and did not turn the camera");
+        state.execute_menu_action("Show Network Pane");
+        assert!(state.show_network);
 
         at(&mut state, cx, cy);
         let pivot = state.viewport().pivot;
@@ -9637,12 +9644,13 @@ mod tests {
             "the suite writes settings inside the real cce config directory"
         );
 
-        // The flip that carried the damage: it marks settings dirty and
-        // `execute_action` saves at the end of the action.
+        // A flip of the kind that carried the damage: it marks settings
+        // dirty and `execute_action` saves at the end of the action. (It was
+        // the network plate's toggle, retired with the plate.)
         let mut state = State::new(false);
-        let plate = state.network_plate;
-        assert!(state.run_command("toggle_network_plate"));
-        assert_ne!(state.network_plate, plate, "the toggle did not flip the plate");
+        let plate = state.params_plate;
+        assert!(state.run_command("toggle_params_plate"));
+        assert_ne!(state.params_plate, plate, "the toggle did not flip the plate");
 
         // Without this the test passes on a save that never happened, which
         // is exactly the bug wearing a different face.
@@ -9727,51 +9735,6 @@ mod tests {
         );
     }
 
-    /// The network plate is optional, and the option is reachable three ways
-    /// that cannot disagree: the View settings node's toggle, the network
-    /// pane's View menu, and the command palette.
-    ///
-    /// The flip itself is exercised by
-    /// `the_suite_does_not_write_the_users_own_settings`, which is what it is
-    /// for: until the settings path was redirected under test, flipping the
-    /// plate here would have rewritten the user's own state.kdl as a side
-    /// effect. What is asserted below is everything around the flip — the
-    /// default, the wiring, and the mirror.
-    #[test]
-    fn test_the_network_plate_is_an_option() {
-        use crate::command::{by_id, Run};
-
-        let state = State::new(false);
-        assert!(!state.network_plate, "the plate is off unless the user turned it on");
-
-        // The command exists, is rebindable, and runs the same action the menu
-        // row does — one implementation behind both.
-        let cmd = by_id("toggle_network_plate").expect("no toggle_network_plate command");
-        assert_eq!(cmd.label, "Network Plate");
-        assert_eq!(cmd.run, Run::Key(crate::shortcut::Action::ToggleNetworkPlate));
-        assert!(cmd.default_chord.is_some(), "the plate toggle has no chord");
-
-        // The live flag and the dialog's switch are one reading: the
-        // command's palette row goes through `command_toggle_state` rather
-        // than mirroring the flag onto a node that could fall out of step
-        // with it.
-        let mut state = State::new(false);
-        assert_eq!(state.command_toggle_state("toggle_network_plate"), Some(false));
-        assert!(state.run_command("toggle_network_plate"));
-        assert!(state.network_plate);
-        assert_eq!(state.command_toggle_state("toggle_network_plate"), Some(true));
-
-        state.run_command("command_palette");
-        let row = state
-            .slots
-            .dialog
-            .rows
-            .iter()
-            .find(|r| r.id == "toggle_network_plate")
-            .expect("a Network Plate row");
-        assert_eq!(row.toggle(), Some(true), "the row's switch reads the live flag");
-    }
-
     /// The viewport guide toggles survive the next parameter edit.
     ///
     /// `apply_settings_from_menubar_subnets` used to copy the Guides utility
@@ -16320,7 +16283,7 @@ mod tests {
             "toggle_grid", "toggle_origin", "toggle_wireframe",
             "toggle_wire_single_color", "toggle_ray_traced_preview",
             "toggle_circular_pane", "toggle_camera_pivot", "toggle_square_viewport",
-            "toggle_network_plate", "toggle_params_plate",
+            "toggle_params_plate",
         ] {
             assert!(crate::command::by_id(id).is_some(), "the toggle '{id}' has no command");
             assert!(state.command_toggle_state(id).is_some(), "the toggle '{id}' draws no switch");
@@ -16351,8 +16314,9 @@ mod tests {
         assert_eq!(state.slots.dialog.query, "");
     }
 
-    /// A left press on empty grid puts the cursor on the pressed cell — on the
-    /// PRESS — and dragging from there expands it into a region, which stays
+    /// A ctrl+left press on empty grid puts the cursor on the pressed cell — on
+    /// the PRESS — and dragging from there expands it into a region (without
+    /// ctrl the press is the scene's, and orbits), which stays
     /// after the release. Moving the cursor any other way collapses it, since
     /// the expanse is only read back while its anchor is the live cursor.
     #[test]
@@ -16360,7 +16324,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{ElementState, MouseButton};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -16383,11 +16346,21 @@ mod tests {
             });
         };
 
+        // A plain press on empty space is the scene's: it orbits, and the
+        // cursor stays. With ctrl it is the grid's.
         move_to(&mut state, anchor);
+        let before = (state.grid_cursor_col, state.grid_cursor_row);
+        state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
+        assert!(state.orbit_drag.is_some() && state.grid_cursor_drag.is_none(), "a plain press orbits");
+        state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
+        assert_eq!((state.grid_cursor_col, state.grid_cursor_row), before);
+        state.modifiers.ctrl = true;
         state.handle_event(&WindowEvent::MouseInput {
             state: ElementState::Pressed,
             button: MouseButton::Left,
         });
+        state.modifiers.ctrl = false;
+        assert_eq!(state.focused_pane, crate::slots::LEFT_MENUBAR_IDX, "the network has focus");
         assert_eq!(
             (state.grid_cursor_col, state.grid_cursor_row),
             anchor,
@@ -16616,7 +16589,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{ElementState, MouseButton};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -16651,10 +16623,13 @@ mod tests {
             });
         };
 
-        // Select a and b by dragging a box round them; it settles onto their
+        // Select a and b by dragging a box round them, ctrl held as the
+        // press lands on empty space; it settles onto their
         // bounding box, (1, 5) to (2, 7).
         move_to(&mut state, (0, 4));
+        state.modifiers.ctrl = true;
         state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
+        state.modifiers.ctrl = false;
         move_to(&mut state, (3, 9));
         state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
         assert_eq!(state.grid_cursor_region(), (1, 5, 2, 3));
@@ -16696,7 +16671,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{ElementState, MouseButton};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -16732,10 +16706,12 @@ mod tests {
             });
         };
         move_to(&mut state, (0, 3));
+        state.modifiers.ctrl = true;
         state.handle_event(&WindowEvent::MouseInput {
             state: ElementState::Pressed,
             button: MouseButton::Left,
         });
+        state.modifiers.ctrl = false;
         move_to(&mut state, (3, 9));
         assert_eq!(state.grid_cursor_region(), (0, 3, 4, 7), "the box as drawn");
         assert_eq!(state.selected_slots(), vec![a, b]);
@@ -17102,95 +17078,6 @@ mod tests {
         assert_eq!(state.grid_cursor_region(), before, "no region grew out of it");
     }
 
-    /// A right press on EMPTY network space opens the network's own context
-    /// menu — until 2026-09-22 it opened the add-node palette outright, which
-    /// left the network the one pane whose right-click was not a context menu,
-    /// and left every other graph-wide command reachable only by chord or
-    /// through the palette. Add Node is the first row, and picking it is what
-    /// opens the palette — at the cell the press landed on, since the press
-    /// moves the grid cursor there before the menu goes up.
-    #[test]
-    fn network_right_click_opens_a_menu_whose_first_row_is_add_node() {
-        use crate::dialog::Mode;
-        use crate::slots::CONTENT_IDX;
-        use crate::window::{LocalPosition, WindowEvent};
-        use cce_ui::widget::{ElementState, MouseButton};
-        let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
-        state.resize(1600.0, 900.0, 1.0);
-        state.rebuild_positions();
-        state.apply_layout();
-
-        // A point in the network pane with no node under it: the plate is on,
-        // so the pane's rect is its own and empty space is still the graph's.
-        assert!(state.network_plate, "the plate is on by default");
-        let (cx, cy, cw, ch) = state.positions[CONTENT_IDX];
-        let (px, py) = (cx + cw * 0.85, cy + ch * 0.85);
-        state.handle_event(&WindowEvent::CursorMoved {
-            position: LocalPosition { x: px as f64, y: py as f64 },
-        });
-        assert!(state.in_network_pane(), "the press must land in the network pane");
-        assert!(
-            state.graph().node_at(px, py).is_none(),
-            "pick a cell with no node on it"
-        );
-        let cell = state.cell_at(px, py);
-
-        state.handle_event(&WindowEvent::MouseInput {
-            state: ElementState::Pressed,
-            button: MouseButton::Right,
-        });
-        assert!(!state.dialog_visible(), "no palette straight off the press");
-        assert!(cce_ui::widget::context_menu::is_visible());
-        let options = cce_ui::widget::context_menu::options();
-        assert_eq!(options.first().map(String::as_str), Some("Add Node"));
-        assert!(
-            options.iter().any(|o| o == "Layout Nodes"),
-            "the graph-wide commands come with it: {options:?}"
-        );
-        assert!(
-            options.iter().any(|o| o.ends_with("Network Plate")),
-            "a toggle row carries its mark: {options:?}"
-        );
-        assert_eq!((state.grid_cursor_col, state.grid_cursor_row), cell);
-
-        // A left click on the first row runs Add Node: the menu goes, the
-        // palette comes up, and it will add at the cell that was clicked.
-        let rx = cce_ui::widget::context_menu::x() + 8.0;
-        let ry = cce_ui::widget::context_menu::row_y(0) + 4.0;
-        state.handle_event(&WindowEvent::CursorMoved {
-            position: LocalPosition { x: rx as f64, y: ry as f64 },
-        });
-        let menu_corner = (cce_ui::widget::context_menu::x(), cce_ui::widget::context_menu::y());
-        state.handle_event(&WindowEvent::MouseInput {
-            state: ElementState::Pressed,
-            button: MouseButton::Left,
-        });
-        assert!(!cce_ui::widget::context_menu::is_visible(), "the pick closes the menu");
-        assert!(state.dialog_visible());
-        assert_eq!(state.slots.dialog.mode, Mode::AddNode);
-        assert_eq!((state.grid_cursor_col, state.grid_cursor_row), cell);
-
-        // The menu turns into the list: the plate opens on the menu's
-        // corner, as far as the window lets it, not centred.
-        let (dx, dy, dw, dh) = state.positions[crate::slots::DIALOG_IDX];
-        let want = crate::dialog::layout_at(state.width, state.height, menu_corner.0, menu_corner.1);
-        assert_eq!((dx, dy, dw, dh), want);
-        assert_ne!((dx, dy), {
-            let (x, y, _, _) = crate::dialog::layout_in(state.width, state.height);
-            (x, y)
-        }, "not the centred plate");
-        assert!(dx + dw <= state.width && dy + dh <= state.height, "inside the window");
-
-        // Tab and the palette's other openings still centre it.
-        state.close_dialog();
-        state.open_node_palette();
-        assert_eq!(
-            state.positions[crate::slots::DIALOG_IDX],
-            crate::dialog::layout_in(state.width, state.height)
-        );
-    }
-
     /// An anchored plate keeps its corner where it fits, gives up height
     /// before it moves, and rises only below the least height it keeps.
     #[test]
@@ -18120,7 +18007,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{scroll_motion::set_scroll_phase, MouseScrollDelta, Position, ScrollPhase};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -18511,7 +18397,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{scroll_motion::set_scroll_phase, MouseScrollDelta, ParametersBg, Position, ScrollPhase};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
@@ -18646,7 +18531,6 @@ mod tests {
         use crate::window::{LocalPosition, WindowEvent};
         use cce_ui::widget::{scroll_motion::set_scroll_phase, MouseScrollDelta, ParametersBg, Position, ScrollPhase};
         let mut state = State::new(false);
-        state.network_plate = true; // written against the plated network
         state.resize(1600.0, 900.0, 1.0);
         state.rebuild_positions();
         state.apply_layout();
diff --git a/src/render.rs b/src/render.rs
index 07cdf5a..4fa5d2b 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -511,9 +511,8 @@ impl State {
             } else {
                 clip
             };
-            // No plate here: the pane's plate is NETWORK_PANEL_IDX's
-            // PassivePlate (the params material, gated on `network_plate`
-            // in the generic arm). Until 2026-09-20 this arm ALSO painted
+            // No plate here, and none behind: the network has no plate
+            // (since 2026-10-06), its nodes standing on the scene. Until 2026-09-20 this arm ALSO painted
             // the graph widget's own background over it — the cell colour
             // at the network opacity, a flat blue-grey wash that predated
             // the plate and made the network pane the one pane with a hue.
@@ -667,8 +666,8 @@ impl State {
                 //
                 // In the accent only while the network has focus, as a
                 // focused plate's rim is; otherwise in the plates' neutral
-                // border colour. With the network plate off this is the
-                // network's ONLY focus cue — no plate, no rim to tint.
+                // border colour. Outside the circular pane this is the
+                // network's ONLY focus cue — it has no plate, no rim to tint.
                 // Without the shader plates the glint is drawn untinted, so
                 // focus adds a flat ring in the accent over it.
                 let focused = self.focused_pane == LEFT_MENUBAR_IDX;
@@ -822,7 +821,7 @@ impl State {
             let (wx, wy, ww2, wh2) = w.rect();
             let network_panel =
                 idx == NETWORK_PANEL_IDX || idx == crate::slots::NETWORK_PANEL2_IDX;
-            if !(network_panel && !self.network_plate) {
+            if !network_panel {
                 append_widget_plate_radii(w, pc, self.plate_focus_tint(idx), self.pane_plate_radii(wx, wy, ww2, wh2));
             }
 
@@ -945,20 +944,9 @@ impl State {
                     );
                     return;
                 }
-                // With its plate off the network spans the window and has no
-                // edge to ring: the grid cursor carries its focus instead.
-                if relief || !self.network_plate {
-                    return;
-                }
-                color[3] *= self.network_opacity;
-                // Whichever network editor is FRONTED owns the ring — pane
-                // 1's rect is zero while it waits as a tab.
-                let p2 = self.positions[crate::slots::NETWORK_PANEL2_IDX];
-                if p2.2 > 0.0 && self.positions[NETWORK_PANEL_IDX].2 <= 0.0 {
-                    p2
-                } else {
-                    self.positions[NETWORK_PANEL_IDX]
-                }
+                // The network has no plate and spans the window, so there
+                // is no edge to ring: the grid cursor carries its focus.
+                return;
             }
             RIGHT_MENUBAR_IDX => {
                 if !self.show_viewport || relief {
diff --git a/src/shortcut.rs b/src/shortcut.rs
index 588529f..ec3d967 100644
--- a/src/shortcut.rs
+++ b/src/shortcut.rs
@@ -77,8 +77,6 @@ pub enum Action {
     FrameAll,
     /// Arrange the current level's nodes from their wiring.
     LayoutNodes,
-    /// Draw the network pane's plate, or let the graph overlay the scene.
-    ToggleNetworkPlate,
     /// Draw the params pane's plate, or stand its controls on the scene.
     ToggleParamsPlate,
     /// Clear the node selection.