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

commitbfe784efa6bcd32ca0e8f2986f9b80c7be75626e
parent0e03fe745e
authorLucas Galante <[email protected]>
date2026-06-13 21:29
Update design interface node configurations, geometry, render, and project logic

 default_project.json      |   96 ++-
 nodes/add.json            |   12 +-
 nodes/box.json            |   38 ++
 nodes/camera.json         |    4 +-
 nodes/input.json          |    7 +
 nodes/line.json           |    4 +-
 nodes/opencl.json         |    8 +-
 nodes/output.json         |    9 +
 nodes/scatter.json        |   25 +
 nodes/sphere.json         |   27 +-
 nodes/subnet.json         |    9 +
 nodes/transform.json      |    4 +-
 project.json              |   96 ++-
 scratch/test_errors.log   | 1507 +++++++++++++++++++++++++++++++++++++++++++++
 scratch/test_errors2.log  | 1044 +++++++++++++++++++++++++++++++
 scratch/test_selection.py |   29 +
 src/app.rs                | 1364 +++++++++++++++++++++++-----------------
 src/geometry.rs           |  739 +++++++++++++++++++++-
 src/main.rs               |  217 ++++++-
 src/project.rs            |  364 +++++------
 src/render.rs             |  242 +++++++-
 src/window.rs             |  101 +--
 22 files changed, 5002 insertions(+), 944 deletions(-)

diff --git a/default_project.json b/default_project.json
index 5ba33f6..3add471 100644
--- a/default_project.json
+++ b/default_project.json
@@ -47,8 +47,11 @@
       },
       {
         "name": "Sphere 1",
-        "type": "sphere",
-        "children": [],
+        "type": "node",
+        "position": [
+          4.0,
+          2.0
+        ],
         "params": [
           {
             "name": "Radius",
@@ -61,40 +64,69 @@
             "step": null
           }
         ],
-        "position": [
-          4.0,
-          2.0
-        ]
-      },
-      {
-        "name": "Transform 1",
-        "type": "transform",
-        "children": [],
-        "params": [
+        "children": [
           {
-            "name": "Input",
-            "label": "",
-            "type": "text",
-            "default": "Sphere 1",
-            "options": [],
-            "min": null,
-            "max": null,
-            "step": null
+            "name": "opencl1",
+            "type": "opencl",
+            "position": [
+              4.0,
+              2.0
+            ],
+            "params": [
+              {
+                "name": "Input",
+                "label": "",
+                "type": "text",
+                "default": "",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              },
+              {
+                "name": "Code",
+                "label": "",
+                "type": "code",
+                "default": "__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n    int id = get_global_id(0);\n    if (id == 0) {\n        float radius = chf(\"Radius\", 0.5f);\n        float center_x = 0.0f;\n        float center_y = 0.55f;\n        float center_z = 0.0f;\n        int lat_steps = 16;\n        int lon_steps = 24;\n        int count = 0;\n        for (int lat = 0; lat < lat_steps; lat++) {\n            float theta0 = 3.14159265f * (float)lat / (float)lat_steps;\n            float theta1 = 3.14159265f * (float)(lat + 1) / (float)lat_steps;\n            for (int lon = 0; lon < lon_steps; lon++) {\n                float phi0 = 6.2831853f * (float)lon / (float)lon_steps;\n                float phi1 = 6.2831853f * (float)(lon + 1) / (float)lon_steps;\n                float x00 = radius * sin(theta0) * cos(phi0);\n                float y00 = radius * cos(theta0);\n                float z00 = radius * sin(theta0) * sin(phi0);\n                float x10 = radius * sin(theta1) * cos(phi0);\n                float y10 = radius * cos(theta1);\n                float z10 = radius * sin(theta1) * sin(phi0);\n                float x11 = radius * sin(theta1) * cos(phi1);\n                float y11 = radius * cos(theta1);\n                float z11 = radius * sin(theta1) * sin(phi1);\n                float x01 = radius * sin(theta0) * cos(phi1);\n                float y01 = radius * cos(theta0);\n                float z01 = radius * sin(theta0) * sin(phi1);\n                float px[6] = {x00, x10, x11, x00, x11, x01};\n                float py[6] = {y00, y10, y11, y00, y11, y01};\n                float pz[6] = {z00, z10, z11, z00, z11, z01};\n                for (int v = 0; v < 6; v++) {\n                    int idx = count++;\n                    if (idx < max_vertices) {\n                        out_pos[idx * 3 + 0] = center_x + px[v];\n                        out_pos[idx * 3 + 1] = center_y + py[v];\n                        out_pos[idx * 3 + 2] = center_z + pz[v];\n                        float nx = px[v];\n                        float ny = py[v];\n                        float nz = pz[v];\n                        float len = sqrt(nx*nx + ny*ny + nz*nz);\n                        if (len > 0.0f) {\n                            nx /= len;\n                            ny /= len;\n                            nz /= len;\n                        }\n                        out_col[idx * 3 + 0] = 0.35f + fabs(nx) * 0.35f;\n                        out_col[idx * 3 + 1] = 0.45f + fabs(ny) * 0.35f;\n                        out_col[idx * 3 + 2] = 0.85f;\n                    }\n                }\n            }\n        }\n        *out_count = count;\n    }\n}",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              },
+              {
+                "name": "Update Parameters",
+                "label": "",
+                "type": "button",
+                "default": "",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              }
+            ],
+            "children": []
           },
           {
-            "name": "Translation",
-            "label": "",
-            "type": "float3",
-            "default": "1.00:0.50:0.00",
-            "options": [],
-            "min": -10.0,
-            "max": 10.0,
-            "step": null
+            "name": "output1",
+            "type": "output",
+            "position": [
+              4.0,
+              3.0
+            ],
+            "params": [
+              {
+                "name": "Input",
+                "label": "",
+                "type": "text",
+                "default": "opencl1",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              }
+            ],
+            "children": []
           }
-        ],
-        "position": [
-          4.0,
-          3.0
         ]
       }
     ],
diff --git a/nodes/add.json b/nodes/add.json
index a1995a8..2d1ab26 100644
--- a/nodes/add.json
+++ b/nodes/add.json
@@ -1,14 +1,16 @@
 {
-  "name": "Add 1",
+  "name": "Add",
   "type": "add",
+  "inputs": 0,
+  "outputs": 1,
   "params": [
     {
       "name": "Points",
-      "default": "100",
       "type": "spinbox",
-      "min": 1.0,
-      "max": 1000.0,
-      "step": 1.0
+      "default": "100",
+      "min": 10.0,
+      "max": 5000.0,
+      "step": 10.0
     }
   ]
 }
diff --git a/nodes/box.json b/nodes/box.json
new file mode 100644
index 0000000..fd45e8e
--- /dev/null
+++ b/nodes/box.json
@@ -0,0 +1,38 @@
+{
+  "name": "Box",
+  "type": "node",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+    { "name": "Input", "default": "", "type": "text" },
+    { "name": "Scale", "default": "1.0", "type": "slider" },
+    { "name": "Wireframe", "default": "false", "type": "toggle" }
+  ],
+  "children": [
+    {
+      "name": "input1",
+      "type": "input",
+      "position": [4.0, 1.0]
+    },
+    {
+      "name": "opencl1",
+      "type": "opencl",
+      "params": [
+        { "name": "Input", "default": "input1" },
+        {
+          "name": "Code",
+          "default": "void add_box(float cx, float cy, float cz, float hx, float hy, float hz, __global float* out_pos, __global float* out_col, int* count, int max_vertices) {\n    float temp_pos[108] = {\n        -hx, -hy, hz,   hx, -hy, hz,   hx,  hy, hz,\n        -hx, -hy, hz,   hx,  hy, hz,  -hx,  hy, hz,\n        -hx, -hy, -hz, -hx,  hy, -hz,  hx,  hy, -hz,\n        -hx, -hy, -hz,  hx,  hy, -hz,  hx, -hy, -hz,\n        -hx, -hy, -hz, -hx, -hy, hz,  -hx,  hy, hz,\n        -hx, -hy, -hz, -hx,  hy, hz,  -hx,  hy, -hz,\n        hx, -hy, -hz,  hx,  hy, -hz,  hx,  hy, hz,\n        hx, -hy, -hz,  hx,  hy, hz,   hx, -hy, hz,\n        -hx,  hy, -hz, -hx,  hy, hz,   hx,  hy, hz,\n        -hx,  hy, -hz,  hx,  hy, hz,   hx,  hy, -hz,\n        -hx, -hy, -hz,  hx, -hy, -hz,  hx, -hy, hz,\n        -hx, -hy, -hz,  hx, -hy, hz,  -hx, -hy, hz\n    };\n    int start = *count;\n    for (int i = 0; i < 36; i++) {\n        int idx = start + i;\n        if (idx < max_vertices) {\n            out_pos[idx * 3 + 0] = cx + temp_pos[i * 3 + 0];\n            out_pos[idx * 3 + 1] = cy + temp_pos[i * 3 + 1];\n            out_pos[idx * 3 + 2] = cz + temp_pos[i * 3 + 2];\n            out_col[idx * 3 + 0] = 0.8f;\n            out_col[idx * 3 + 1] = 0.2f;\n            out_col[idx * 3 + 2] = 0.2f;\n        }\n    }\n    *count = start + 36;\n}\n\n__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n    int id = get_global_id(0);\n    if (id == 0) {\n        float scale = chf(\"Scale\", 1.0f);\n        int wireframe = chb(\"Wireframe\", 0);\n        float dx = 0.5f * scale;\n        float dy = 0.5f * scale;\n        float dz = 0.5f * scale;\n        float center_x = 0.0f;\n        float center_y = 0.55f;\n        float center_z = 0.0f;\n        int count = 0;\n        if (wireframe) {\n            float t_line = 0.008f * scale;\n            float t_corner = 0.012f * scale;\n            for (int ix = 0; ix < 2; ix++) {\n                float cx = center_x + (ix == 0 ? -dx : dx);\n                for (int iy = 0; iy < 2; iy++) {\n                    float cy = center_y + (iy == 0 ? -dy : dy);\n                    for (int iz = 0; iz < 2; iz++) {\n                        float cz = center_z + (iz == 0 ? -dz : dz);\n                        add_box(cx, cy, cz, t_corner, t_corner, t_corner, out_pos, out_col, &count, max_vertices);\n                    }\n                }\n            }\n            for (int iy = 0; iy < 2; iy++) {\n                float cy = center_y + (iy == 0 ? -dy : dy);\n                for (int iz = 0; iz < 2; iz++) {\n                    float cz = center_z + (iz == 0 ? -dz : dz);\n                    add_box(center_x, cy, cz, dx, t_line, t_line, out_pos, out_col, &count, max_vertices);\n                }\n            }\n            for (int ix = 0; ix < 2; ix++) {\n                float cx = center_x + (ix == 0 ? -dx : dx);\n                for (int iz = 0; iz < 2; iz++) {\n                    float cz = center_z + (iz == 0 ? -dz : dz);\n                    add_box(cx, center_y, cz, t_line, dy, t_line, out_pos, out_col, &count, max_vertices);\n                }\n            }\n            for (int ix = 0; ix < 2; ix++) {\n                float cx = center_x + (ix == 0 ? -dx : dx);\n                for (int iy = 0; iy < 2; iy++) {\n                    float cy = center_y + (iy == 0 ? -dy : dy);\n                    add_box(cx, cy, center_z, t_line, t_line, dz, out_pos, out_col, &count, max_vertices);\n                }\n            }\n        } else {\n            add_box(center_x, center_y, center_z, dx, dy, dz, out_pos, out_col, &count, max_vertices);\n        }\n        *out_count = count;\n    }\n}"
+        }
+      ],
+      "position": [4.0, 2.0]
+    },
+    {
+      "name": "output1",
+      "type": "output",
+      "params": [
+        { "name": "Input", "default": "opencl1" }
+      ],
+      "position": [4.0, 3.0]
+    }
+  ]
+}
diff --git a/nodes/camera.json b/nodes/camera.json
index 59b3324..50c3f14 100644
--- a/nodes/camera.json
+++ b/nodes/camera.json
@@ -1,6 +1,8 @@
 {
-  "name": "Camera 1",
+  "name": "Camera",
   "type": "camera",
+  "inputs": 0,
+  "outputs": 0,
   "params": [
     { "name": "Position", "type": "float3", "default": "2.50:1.80:2.50", "min": -10.0, "max": 10.0 },
     { "name": "Rotation", "type": "float3", "default": "0.00:0.00:0.00", "min": -180.0, "max": 180.0 },
diff --git a/nodes/input.json b/nodes/input.json
new file mode 100644
index 0000000..1b48b51
--- /dev/null
+++ b/nodes/input.json
@@ -0,0 +1,7 @@
+{
+  "name": "Input",
+  "type": "input",
+  "inputs": 0,
+  "outputs": 1,
+  "params": []
+}
diff --git a/nodes/line.json b/nodes/line.json
index bed2c6c..c512f8d 100644
--- a/nodes/line.json
+++ b/nodes/line.json
@@ -1,6 +1,8 @@
 {
-  "name": "Line 1",
+  "name": "Line",
   "type": "line",
+  "inputs": 0,
+  "outputs": 1,
   "params": [
     { "name": "Length", "default": "1.0", "type": "slider" },
     { "name": "Thickness", "default": "0.02", "type": "slider" }
diff --git a/nodes/opencl.json b/nodes/opencl.json
index e65898f..8d913f8 100644
--- a/nodes/opencl.json
+++ b/nodes/opencl.json
@@ -1,11 +1,15 @@
 {
-  "name": "OpenCL 1",
+  "name": "OpenCL",
   "type": "opencl",
+  "inputs": 1,
+  "outputs": 1,
   "params": [
+    { "name": "Input", "default": "", "type": "text" },
     {
       "name": "Code",
       "type": "code",
       "default": "__kernel void process(__global float* pos, __global float* col, int count) {\n    int id = get_global_id(0);\n    if (id < count) {\n        // Default deformation kernel (sine wave deforming Y based on X)\n        pos[id * 3 + 1] += sin(pos[id * 3] * 4.0f) * 0.15f;\n    }\n}"
-    }
+    },
+    { "name": "Update Parameters", "type": "button", "default": "" }
   ]
 }
diff --git a/nodes/output.json b/nodes/output.json
new file mode 100644
index 0000000..d0a33e7
--- /dev/null
+++ b/nodes/output.json
@@ -0,0 +1,9 @@
+{
+  "name": "Output",
+  "type": "output",
+  "inputs": 1,
+  "outputs": 0,
+  "params": [
+    { "name": "Input", "default": "", "type": "text" }
+  ]
+}
diff --git a/nodes/scatter.json b/nodes/scatter.json
new file mode 100644
index 0000000..269a284
--- /dev/null
+++ b/nodes/scatter.json
@@ -0,0 +1,25 @@
+{
+  "name": "Scatter",
+  "type": "scatter",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+    { "name": "Input", "default": "", "type": "text" },
+    {
+      "name": "Points",
+      "type": "spinbox",
+      "default": "100",
+      "min": 10.0,
+      "max": 5000.0,
+      "step": 10.0
+    },
+    {
+      "name": "Radius",
+      "type": "slider",
+      "default": "0.02",
+      "min": 0.005,
+      "max": 0.1,
+      "step": 0.005
+    }
+  ]
+}
diff --git a/nodes/sphere.json b/nodes/sphere.json
index be69a94..0a5409a 100644
--- a/nodes/sphere.json
+++ b/nodes/sphere.json
@@ -1,7 +1,30 @@
 {
-  "name": "Sphere 1",
-  "type": "sphere",
+  "name": "Sphere",
+  "type": "node",
+  "inputs": 0,
+  "outputs": 1,
   "params": [
     { "name": "Radius", "default": "0.5", "type": "slider" }
+  ],
+  "children": [
+    {
+      "name": "opencl1",
+      "type": "opencl",
+      "params": [
+        {
+          "name": "Code",
+          "default": "__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n    int id = get_global_id(0);\n    if (id == 0) {\n        float radius = chf(\"Radius\", 0.5f);\n        float center_x = 0.0f;\n        float center_y = 0.55f;\n        float center_z = 0.0f;\n        int lat_steps = 16;\n        int lon_steps = 24;\n        int count = 0;\n        for (int lat = 0; lat < lat_steps; lat++) {\n            float theta0 = 3.14159265f * (float)lat / (float)lat_steps;\n            float theta1 = 3.14159265f * (float)(lat + 1) / (float)lat_steps;\n            for (int lon = 0; lon < lon_steps; lon++) {\n                float phi0 = 6.2831853f * (float)lon / (float)lon_steps;\n                float phi1 = 6.2831853f * (float)(lon + 1) / (float)lon_steps;\n                float x00 = radius * sin(theta0) * cos(phi0);\n                float y00 = radius * cos(theta0);\n                float z00 = radius * sin(theta0) * sin(phi0);\n                float x10 = radius * sin(theta1) * cos(phi0);\n                float y10 = radius * cos(theta1);\n                float z10 = radius * sin(theta1) * sin(phi0);\n                float x11 = radius * sin(theta1) * cos(phi1);\n                float y11 = radius * cos(theta1);\n                float z11 = radius * sin(theta1) * sin(phi1);\n                float x01 = radius * sin(theta0) * cos(phi1);\n                float y01 = radius * cos(theta0);\n                float z01 = radius * sin(theta0) * sin(phi1);\n                float px[6] = {x00, x10, x11, x00, x11, x01};\n                float py[6] = {y00, y10, y11, y00, y11, y01};\n                float pz[6] = {z00, z10, z11, z00, z11, z01};\n                for (int v = 0; v < 6; v++) {\n                    int idx = count++;\n                    if (idx < max_vertices) {\n                        out_pos[idx * 3 + 0] = center_x + px[v];\n                        out_pos[idx * 3 + 1] = center_y + py[v];\n                        out_pos[idx * 3 + 2] = center_z + pz[v];\n                        float nx = px[v];\n                        float ny = py[v];\n                        float nz = pz[v];\n                        float len = sqrt(nx*nx + ny*ny + nz*nz);\n                        if (len > 0.0f) {\n                            nx /= len;\n                            ny /= len;\n                            nz /= len;\n                        }\n                        out_col[idx * 3 + 0] = 0.35f + fabs(nx) * 0.35f;\n                        out_col[idx * 3 + 1] = 0.45f + fabs(ny) * 0.35f;\n                        out_col[idx * 3 + 2] = 0.85f;\n                    }\n                }\n            }\n        }\n        *out_count = count;\n    }\n}"
+        }
+      ],
+      "position": [4.0, 2.0]
+    },
+    {
+      "name": "output1",
+      "type": "output",
+      "params": [
+        { "name": "Input", "default": "opencl1" }
+      ],
+      "position": [4.0, 3.0]
+    }
   ]
 }
diff --git a/nodes/subnet.json b/nodes/subnet.json
new file mode 100644
index 0000000..df329ae
--- /dev/null
+++ b/nodes/subnet.json
@@ -0,0 +1,9 @@
+{
+  "name": "Subnet",
+  "type": "node",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+    { "name": "Input", "default": "", "type": "text" }
+  ]
+}
diff --git a/nodes/transform.json b/nodes/transform.json
index cce62ca..fde13df 100644
--- a/nodes/transform.json
+++ b/nodes/transform.json
@@ -1,6 +1,8 @@
 {
-  "name": "Transform 1",
+  "name": "Transform",
   "type": "transform",
+  "inputs": 1,
+  "outputs": 1,
   "params": [
     { "name": "Input", "default": "", "type": "text" },
     { "name": "Translation", "default": "0.00:0.00:0.00", "type": "float3", "min": -10.0, "max": 10.0 }
diff --git a/project.json b/project.json
index 3e53fbe..1b466ca 100644
--- a/project.json
+++ b/project.json
@@ -47,8 +47,11 @@
       },
       {
         "name": "Sphere 1",
-        "type": "sphere",
-        "children": [],
+        "type": "node",
+        "position": [
+          4.0,
+          2.0
+        ],
         "params": [
           {
             "name": "Radius",
@@ -61,40 +64,69 @@
             "step": null
           }
         ],
-        "position": [
-          4.0,
-          2.0
-        ]
-      },
-      {
-        "name": "Transform 1",
-        "type": "transform",
-        "children": [],
-        "params": [
+        "children": [
           {
-            "name": "Input",
-            "label": "",
-            "type": "text",
-            "default": "Sphere 1",
-            "options": [],
-            "min": null,
-            "max": null,
-            "step": null
+            "name": "opencl1",
+            "type": "opencl",
+            "position": [
+              4.0,
+              2.0
+            ],
+            "params": [
+              {
+                "name": "Input",
+                "label": "",
+                "type": "text",
+                "default": "",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              },
+              {
+                "name": "Code",
+                "label": "",
+                "type": "code",
+                "default": "__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n    int id = get_global_id(0);\n    if (id == 0) {\n        float radius = chf(\"Radius\", 0.5f);\n        float center_x = 0.0f;\n        float center_y = 0.55f;\n        float center_z = 0.0f;\n        int lat_steps = 16;\n        int lon_steps = 24;\n        int count = 0;\n        for (int lat = 0; lat < lat_steps; lat++) {\n            float theta0 = 3.14159265f * (float)lat / (float)lat_steps;\n            float theta1 = 3.14159265f * (float)(lat + 1) / (float)lat_steps;\n            for (int lon = 0; lon < lon_steps; lon++) {\n                float phi0 = 6.2831853f * (float)lon / (float)lon_steps;\n                float phi1 = 6.2831853f * (float)(lat + 1) / (float)lon_steps;\n                float x00 = radius * sin(theta0) * cos(phi0);\n                float y00 = radius * cos(theta0);\n                float z00 = radius * sin(theta0) * sin(phi0);\n                float x10 = radius * sin(theta1) * cos(phi0);\n                float y10 = radius * cos(theta1);\n                float z10 = radius * sin(theta1) * sin(phi0);\n                float x11 = radius * sin(theta1) * cos(phi1);\n                float y11 = radius * cos(theta1);\n                float z11 = radius * sin(theta1) * sin(phi1);\n                float x01 = radius * sin(theta0) * cos(phi1);\n                float y01 = radius * cos(theta0);\n                float z01 = radius * sin(theta0) * sin(phi1);\n                float px[6] = {x00, x10, x11, x00, x11, x01};\n                float py[6] = {y00, y10, y11, y00, y11, y01};\n                float pz[6] = {z00, z10, z11, z00, z11, z01};\n                for (int v = 0; v < 6; v++) {\n                    int idx = count++;\n                    if (idx < max_vertices) {\n                        out_pos[idx * 3 + 0] = center_x + px[v];\n                        out_pos[idx * 3 + 1] = center_y + py[v];\n                        out_pos[idx * 3 + 2] = center_z + pz[v];\n                        float nx = px[v];\n                        float ny = py[v];\n                        float nz = pz[v];\n                        float len = sqrt(nx*nx + ny*ny + nz*nz);\n                        if (len > 0.0f) {\n                            nx /= len;\n                            ny /= len;\n                            nz /= len;\n                        }\n                        out_col[idx * 3 + 0] = 0.35f + fabs(nx) * 0.35f;\n                        out_col[idx * 3 + 1] = 0.45f + fabs(ny) * 0.35f;\n                        out_col[idx * 3 + 2] = 0.85f;\n                    }\n                }\n            }\n        }\n        *out_count = count;\n    }\n}",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              },
+              {
+                "name": "Update Parameters",
+                "label": "",
+                "type": "button",
+                "default": "",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              }
+            ],
+            "children": []
           },
           {
-            "name": "Translation",
-            "label": "",
-            "type": "float3",
-            "default": "1.00:0.50:0.00",
-            "options": [],
-            "min": -10.0,
-            "max": 10.0,
-            "step": null
+            "name": "output1",
+            "type": "output",
+            "position": [
+              4.0,
+              3.0
+            ],
+            "params": [
+              {
+                "name": "Input",
+                "label": "",
+                "type": "text",
+                "default": "opencl1",
+                "options": [],
+                "min": null,
+                "max": null,
+                "step": null
+              }
+            ],
+            "children": []
           }
-        ],
-        "position": [
-          4.0,
-          3.0
         ]
       }
     ],
diff --git a/scratch/test_errors.log b/scratch/test_errors.log
new file mode 100644
index 0000000..2c019a6
--- /dev/null
+++ b/scratch/test_errors.log
@@ -0,0 +1,1507 @@
+warning: unused import: `KeyEvent`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:3:5
+  |
+3 |     KeyEvent, MouseButton, ElementState, focus, Slider, Event, UiContext,
+  |     ^^^^^^^^
+  |
+  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused import: `Ordering`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/mod.rs:67:38
+   |
+67 | use std::sync::atomic::{AtomicUsize, Ordering};
+   |                                      ^^^^^^^^
+
+warning: unused import: `wl_shm`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:24:61
+   |
+24 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_s...
+   |                                                             ^^^^^^
+
+warning: unused imports: `SwashCache`, `TextAtlas`, and `TextRenderer`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:34:36
+   |
+34 |     Cache, FontSystem, Resolution, SwashCache, TextArea, TextAtlas,
+   |                                    ^^^^^^^^^^            ^^^^^^^^^
+35 |     TextBounds, TextRenderer, Viewport, Buffer, Attrs, Metrics,
+   |                 ^^^^^^^^^^^^
+
+warning: unused imports: `KeyEvent` and `MouseScrollDelta`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/context.rs:2:57
+  |
+2 | ...e, Key, KeyEvent, MouseButton, ElementState, MouseScrollDelta};
+  |            ^^^^^^^^                             ^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::widget::TextBox`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/context.rs:5:5
+  |
+5 | use crate::widget::TextBox;
+  |     ^^^^^^^^^^^^^^^^^^^^^^
+
+warning: variable does not need to be mutable
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:925:13
+    |
+925 | ...   let mut adapter = WgpuAdapter::new(display_ptr, surface_ptr, pw, ...
+    |           ----^^^^^^^
+    |           |
+    |           help: remove this `mut`
+    |
+    = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:381:39
+    |
+381 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+    |
+    = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `x`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:440:49
+    |
+440 |             Event::MouseButton { button, state, x, y } => {
+    |                                                 ^ help: try ignoring the field: `x: _`
+
+warning: unused variable: `y`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:440:52
+    |
+440 |             Event::MouseButton { button, state, x, y } => {
+    |                                                    ^ help: try ignoring the field: `y: _`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button.rs:98:30
+   |
+98 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/slider.rs:200:75
+    |
+200 | ...Delta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/slider.rs:307:52
+    |
+307 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/spinbox.rs:209:52
+    |
+209 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/color_selector.rs:158:34
+    |
+158 |     fn tick(&mut self, _dt: f32, ctx: &mut UiContext) -> bool {
+    |                                  ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/color_selector.rs:201:52
+    |
+201 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/dropdown.rs:264:52
+    |
+264 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:565:52
+    |
+565 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:918:39
+    |
+918 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:923:48
+    |
+923 | ...h_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/trackpad.rs:147:91
+    |
+147 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button_strip.rs:216:91
+    |
+216 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: value assigned to `next` is never read
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button_strip.rs:359:24
+    |
+359 |         let mut next = current;
+    |                        ^^^^^^^
+    |
+    = help: maybe it is overwritten before being read?
+    = note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:25:22
+   |
+25 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'sta...
+   |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:26:76
+   |
+26 | ... (dyn Element + 'static)>, ctx: &mut UiContext) { self.parent = paren...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:27:24
+   |
+27 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'stat...
+   |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:28:66
+   |
+28 | ...t (dyn Element + 'static), ctx: &mut UiContext) { self.children.push(...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:29:34
+   |
+29 | ... clear_children(&mut self, ctx: &mut UiContext) { self.children.clear...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:577:23
+    |
+577 |     fn focused(&self, ctx: &UiContext) -> bool {
+    |                       ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1258:22
+     |
+1258 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 's...
+     |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1262:76
+     |
+1262 | ...dyn Element + 'static)>, ctx: &mut UiContext) {
+     |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1270:23
+     |
+1270 |     fn focused(&self, ctx: &UiContext) -> bool {
+     |                       ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/breadcrumb.rs:60:92
+   |
+60 | ...tState, px: f32, _py: f32, ctx: &mut UiContext) -> bool {
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/spreadsheet.rs:233:33
+    |
+233 |     fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
+    |                                 ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:68:31
+   |
+68 |     fn highlight_color(&self, ctx: &UiContext) -> Option<[f32; 4]> { None }
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:149:52
+    |
+149 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:191:22
+    |
+191 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'st...
+    |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:192:76
+    |
+192 | ...dyn Element + 'static)>, ctx: &mut UiContext) { self.parent = parent; }
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:193:24
+    |
+193 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'sta...
+    |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:194:66
+    |
+194 | ...(dyn Element + 'static), ctx: &mut UiContext) { self.children.push(c...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:195:34
+    |
+195 | ...lear_children(&mut self, ctx: &mut UiContext) { self.children.clear(...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:719:39
+    |
+719 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:967:22
+    |
+967 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'st...
+    |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:971:76
+    |
+971 | ...dyn Element + 'static)>, ctx: &mut UiContext) {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:975:24
+    |
+975 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'sta...
+    |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:984:67
+    |
+984 | ...(dyn Element + 'static), ctx: &mut UiContext) {}
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:985:34
+    |
+985 |     fn clear_children(&mut self, ctx: &mut UiContext) {}
+    |                                  ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/panel.rs:37:30
+   |
+37 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/float3.rs:101:91
+    |
+101 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/float3.rs:152:52
+    |
+152 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/progress_bar.rs:25:30
+   |
+25 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/list_item.rs:48:30
+   |
+48 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/graph.rs:253:53
+    |
+253 | ... self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/graph.rs:267:91
+    |
+267 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/usage_bar.rs:35:30
+   |
+35 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/layout_preview.rs:55:30
+   |
+55 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/font_preview.rs:26:30
+   |
+26 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/info_box.rs:25:30
+   |
+25 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/status_dot.rs:32:30
+   |
+32 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: variable does not need to be mutable
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/layout.rs:1918:31
+     |
+1918 |     pub fn with_section_count(mut self, count: usize) -> Self {
+     |                               ----^^^^
+     |                               |
+     |                               help: remove this `mut`
+
+warning: field `min_col_width` is never read
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/layout.rs:1793:5
+     |
+1791 | pub struct GridLayout {
+     |            ---------- field in this struct
+1792 |     grid: Option<Grid>,
+1793 |     min_col_width: f32,
+     |     ^^^^^^^^^^^^^
+     |
+     = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
+
+warning: `clear-ui` (lib) generated 61 warnings (run `cargo fix --lib -p clear-ui` to apply 59 suggestions)
+   Compiling cce-design-interface v0.1.0 (/home/lsgalante/Dropbox/Clear/cce-design-interface)
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:606:20
+    |
+606 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, false), RIGHT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:606:42
+    |
+606 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, false), RIGHT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:606:93
+    |
+606 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, false), RIGHT_MENUBAR_IDX);
+    |                                                                                             ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:607:20
+    |
+607 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, false), PARAM_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:607:42
+    |
+607 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, false), PARAM_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:607:94
+    |
+607 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, false), PARAM_MENUBAR_IDX);
+    |                                                                                              ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:608:20
+    |
+608 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, false), LEFT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:608:42
+    |
+608 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, false), LEFT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:608:94
+    |
+608 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, false), LEFT_MENUBAR_IDX);
+    |                                                                                              ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:611:20
+    |
+611 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, false), RIGHT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:611:42
+    |
+611 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, false), RIGHT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:611:92
+    |
+611 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, false), RIGHT_MENUBAR_IDX);
+    |                                                                                            ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:612:20
+    |
+612 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, false), PARAM_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:612:42
+    |
+612 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, false), PARAM_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:612:93
+    |
+612 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, false), PARAM_MENUBAR_IDX);
+    |                                                                                             ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:613:20
+    |
+613 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, false), SPREADSHEET_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:613:42
+    |
+613 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, false), SPREADSHEET_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `SPREADSHEET_MENUBAR_IDX` in this scope
+   --> src/main.rs:613:93
+    |
+613 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, false), SPREADSHEET_MENUBAR_IDX);
+    |                                                                                             ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::SPREADSHEET_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:614:20
+    |
+614 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, false), LEFT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `SPREADSHEET_MENUBAR_IDX` in this scope
+   --> src/main.rs:614:42
+    |
+614 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, false), LEFT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::SPREADSHEET_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:614:99
+    |
+614 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, false), LEFT_MENUBAR_IDX);
+    |                                                                                                   ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:617:20
+    |
+617 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, true), PARAM_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:617:42
+    |
+617 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, true), PARAM_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:617:92
+    |
+617 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, false, true), PARAM_MENUBAR_IDX);
+    |                                                                                            ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:618:20
+    |
+618 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, true), RIGHT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:618:42
+    |
+618 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, true), RIGHT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:618:93
+    |
+618 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, false, true), RIGHT_MENUBAR_IDX);
+    |                                                                                             ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:619:20
+    |
+619 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, true), LEFT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:619:42
+    |
+619 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, true), LEFT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:619:93
+    |
+619 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, false, true), LEFT_MENUBAR_IDX);
+    |                                                                                             ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:622:20
+    |
+622 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, true), SPREADSHEET_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:622:42
+    |
+622 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, true), SPREADSHEET_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `SPREADSHEET_MENUBAR_IDX` in this scope
+   --> src/main.rs:622:91
+    |
+622 |         assert_eq!(get_next_visible_pane(LEFT_MENUBAR_IDX, true, true, true, true, true), SPREADSHEET_MENUBAR_IDX);
+    |                                                                                           ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::SPREADSHEET_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:623:20
+    |
+623 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, true), PARAM_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `SPREADSHEET_MENUBAR_IDX` in this scope
+   --> src/main.rs:623:42
+    |
+623 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, true), PARAM_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::SPREADSHEET_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:623:98
+    |
+623 |         assert_eq!(get_next_visible_pane(SPREADSHEET_MENUBAR_IDX, true, true, true, true, true), PARAM_MENUBAR_IDX);
+    |                                                                                                  ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:624:20
+    |
+624 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, true), RIGHT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `PARAM_MENUBAR_IDX` in this scope
+   --> src/main.rs:624:42
+    |
+624 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, true), RIGHT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::PARAM_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:624:92
+    |
+624 |         assert_eq!(get_next_visible_pane(PARAM_MENUBAR_IDX, true, true, true, true, true), RIGHT_MENUBAR_IDX);
+    |                                                                                            ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find function `get_next_visible_pane` in this scope
+   --> src/main.rs:625:20
+    |
+625 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, true), LEFT_MENUBAR_IDX);
+    |                    ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this function
+    |
+392 +     use crate::app::get_next_visible_pane;
+    |
+
+error[E0425]: cannot find value `RIGHT_MENUBAR_IDX` in this scope
+   --> src/main.rs:625:42
+    |
+625 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, true), LEFT_MENUBAR_IDX);
+    |                                          ^^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::RIGHT_MENUBAR_IDX;
+    |
+
+error[E0425]: cannot find value `LEFT_MENUBAR_IDX` in this scope
+   --> src/main.rs:625:92
+    |
+625 |         assert_eq!(get_next_visible_pane(RIGHT_MENUBAR_IDX, true, true, true, true, true), LEFT_MENUBAR_IDX);
+    |                                                                                            ^^^^^^^^^^^^^^^^ not found in this scope
+    |
+help: consider importing this constant
+    |
+392 +     use crate::app::LEFT_MENUBAR_IDX;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:824:22
+    |
+824 |         let ctrl_g = Shortcut::parse("Ctrl+g").unwrap();
+    |                      ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:832:23
+    |
+832 |         let complex = Shortcut::parse("Ctrl+Shift+Alt+Logo+s").unwrap();
+    |                       ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:840:22
+    |
+840 |         let tab_sc = Shortcut::parse("Tab").unwrap();
+    |                      ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:844:23
+    |
+844 |         let case_sc = Shortcut::parse("cTrL+sHiFt+ArrowDown").unwrap();
+    |                       ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `ShortcutManager` in this scope
+   --> src/main.rs:850:23
+    |
+850 |         let mut mgr = ShortcutManager::new();
+    |                       ^^^^^^^^^^^^^^^ use of undeclared type `ShortcutManager`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::ShortcutManager;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:851:32
+    |
+851 |         mgr.register("Ctrl+g", Action::ToggleGrid).unwrap();
+    |                                ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+851 -         mgr.register("Ctrl+g", Action::ToggleGrid).unwrap();
+851 +         mgr.register("Ctrl+g", Option::ToggleGrid).unwrap();
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:852:27
+    |
+852 |         mgr.register("`", Action::ToggleSpreadsheet).unwrap();
+    |                           ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+852 -         mgr.register("`", Action::ToggleSpreadsheet).unwrap();
+852 +         mgr.register("`", Option::ToggleSpreadsheet).unwrap();
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:857:63
+    |
+857 |         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Action::ToggleGrid));
+    |                                                               ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+857 -         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Action::ToggleGrid));
+857 +         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Option::ToggleGrid));
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:866:66
+    |
+866 |         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Action::ToggleSpreadsheet));
+    |                                                                  ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+866 -         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Action::ToggleSpreadsheet));
+866 +         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Option::ToggleSpreadsheet));
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+warning: unused import: `std::time::Instant`
+ --> src/main.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+  |
+  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused imports: `Deserialize` and `Serialize`
+ --> src/main.rs:7:13
+  |
+7 | use serde::{Deserialize, Serialize};
+  |             ^^^^^^^^^^^  ^^^^^^^^^
+
+warning: unused imports: `ElementState`, `KeyEvent`, `MouseButton`, and `MouseScrollDelta`
+ --> src/main.rs:9:24
+  |
+9 | use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+  |                        ^^^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused imports: `Capability`, `CompositorHandler`, `CursorIcon`, `PointerHandler`, `ProvidesRegistryState`, `SeatHandler`, `ShmHandler`, `ThemeSpec`, `ThemedPointer`, `Window as XdgWindow`, `WindowConfigure`, `WindowDecorations`, `WindowHandler`, `delegate_compositor`, `delegate_keyboard`, `delegate_output`, `delegate_pointer`, `delegate_registry`, `delegate_seat`, `delegate_shm`, `delegate_xdg_shell`, `delegate_xdg_window`, and `keyboard::KeyboardHandler`
+  --> src/main.rs:12:18
+   |
+12 |     compositor::{CompositorHandler, CompositorState},
+   |                  ^^^^^^^^^^^^^^^^^
+13 |     delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+   |     ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+14 |     delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
+15 |     registry::{ProvidesRegistryState, RegistryState},
+   |                ^^^^^^^^^^^^^^^^^^^^^
+...
+18 |         keyboard::KeyboardHandler,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+19 |         pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
+   |                   ^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^
+20 |         Capability, SeatHandler, SeatState,
+   |         ^^^^^^^^^^  ^^^^^^^^^^^
+...
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                      ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+...
+29 |     shm::{Shm, ShmHandler},
+   |                ^^^^^^^^^^
+
+warning: unused imports: `Proxy`, `QueueHandle`, `wl_keyboard`, `wl_output`, `wl_pointer`, `wl_seat`, `wl_shm`, and `wl_surface`
+  --> src/main.rs:33:16
+   |
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^  ^^^^^^^^^^
+34 |     Connection, QueueHandle, Proxy,
+   |                 ^^^^^^^^^^^  ^^^^^
+
+warning: unused import: `wgpu::util::DeviceExt`
+  --> src/main.rs:38:5
+   |
+38 | use wgpu::util::DeviceExt;
+   |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Breadcrumb`, `Button`, `Canvas`, `Checkbox`, `ColorSelector`, `GraphNode`, `Graph`, `Label`, `MenuBar`, `Paginator`, `ParametersBg`, `Plate`, `ScrollingList`, `Slider`, `Spinbox`, `Splitter`, `Spreadsheet`, `StatusBar`, `Switcher`, `TextLabel`, and `ViewportBg`
+  --> src/main.rs:39:24
+   |
+39 | ...::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |       ^^^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^  ^^^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^           ^^^^^^^^^  ^^^^^  ^^^^^^^^^  ^^^^^^  ^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^^^^^^^^^  ^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `clear_ui::colors`
+  --> src/main.rs:40:5
+   |
+40 | use clear_ui::colors;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Attrs`, `Buffer`, `Cache`, `FontSystem`, `Metrics`, `Resolution`, `SwashCache`, `TextArea`, `TextAtlas`, `TextBounds`, `TextRenderer`, and `Viewport`
+  --> src/main.rs:41:15
+   |
+41 | ...::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
+   |       ^^^^^  ^^^^^^  ^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `Mat4`
+  --> src/main.rs:42:12
+   |
+42 | use glam::{Mat4, Vec3};
+   |            ^^^^
+
+warning: unused import: `std::net::TcpListener`
+ --> src/app.rs:4:5
+  |
+4 | use std::net::TcpListener;
+  |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `BufReader`
+ --> src/app.rs:5:24
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                        ^^^^^^^^^
+
+warning: unused imports: `Capability`, `CompositorHandler`, `CursorIcon`, `OutputHandler`, `OutputState`, `PointerHandler`, `ProvidesRegistryState`, `RegistryState`, `SeatHandler`, `SeatState`, `ShmHandler`, `Shm`, `ThemeSpec`, `ThemedPointer`, `WindowConfigure`, `delegate_compositor`, `delegate_keyboard`, `delegate_output`, `delegate_pointer`, `delegate_registry`, `delegate_seat`, `delegate_shm`, `delegate_xdg_shell`, `delegate_xdg_window`, and `keyboard::KeyboardHandler`
+  --> src/app.rs:12:18
+   |
+12 |     compositor::{CompositorHandler, CompositorState},
+   |                  ^^^^^^^^^^^^^^^^^
+13 |     delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+   |     ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+14 |     delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
+15 |     registry::{ProvidesRegistryState, RegistryState},
+   |                ^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^
+16 |     output::{OutputHandler, OutputState},
+   |              ^^^^^^^^^^^^^  ^^^^^^^^^^^
+17 |     seat::{
+18 |         keyboard::KeyboardHandler,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+19 |         pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
+   |                   ^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^
+20 |         Capability, SeatHandler, SeatState,
+   |         ^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^
+...
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                           ^^^^^^^^^^^^^^^
+...
+29 |     shm::{Shm, ShmHandler},
+   |           ^^^  ^^^^^^^^^^
+
+warning: unused imports: `globals::registry_queue_init`, `wl_keyboard`, `wl_output`, `wl_pointer`, `wl_seat`, and `wl_shm`
+  --> src/app.rs:32:5
+   |
+32 |     globals::registry_queue_init,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^
+
+warning: unused import: `calloop_wayland_source::WaylandSource`
+  --> src/app.rs:36:5
+   |
+36 | use calloop_wayland_source::WaylandSource;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `Switcher`
+  --> src/app.rs:39:241
+   |
+39 | ...kbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |                                                                ^^^^^^^^
+
+warning: unused imports: `SwashCache`, `TextArea`, and `TextBounds`
+  --> src/app.rs:42:70
+   |
+42 | use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, View...
+   |                                                                      ^^^^^^^^^^  ^^^^^^^^             ^^^^^^^^^^
+
+warning: unused import: `crate::project::*`
+  --> src/app.rs:46:5
+   |
+46 | use crate::project::*;
+   |     ^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::render::*`
+  --> src/app.rs:47:5
+   |
+47 | use crate::render::*;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused import: `Shortcut`
+  --> src/app.rs:48:23
+   |
+48 | use crate::shortcut::{Shortcut, ShortcutManager, Action};
+   |                       ^^^^^^^^
+
+warning: unused import: `LocalPosition`
+  --> src/app.rs:51:44
+   |
+51 | use crate::window::{AppState, WindowEvent, LocalPosition};
+   |                                            ^^^^^^^^^^^^^
+
+warning: unused import: `clear_ui::engine::Vertex`
+ --> src/graphics.rs:1:5
+  |
+1 | use clear_ui::engine::Vertex;
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/window.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::fs`
+ --> src/window.rs:2:5
+  |
+2 | use std::fs;
+  |     ^^^^^^^
+
+warning: unused import: `std::net::TcpListener`
+ --> src/window.rs:4:5
+  |
+4 | use std::net::TcpListener;
+  |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `BufReader` and `Write`
+ --> src/window.rs:5:24
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                        ^^^^^^^^^        ^^^^^
+
+warning: unused imports: `Deserialize` and `Serialize`
+ --> src/window.rs:7:13
+  |
+7 | use serde::{Deserialize, Serialize};
+  |             ^^^^^^^^^^^  ^^^^^^^^^
+
+warning: unused imports: `ElementState`, `KeyEvent`, `Key`, `MouseButton`, `MouseScrollDelta`, and `NamedKey`
+ --> src/window.rs:9:24
+  |
+9 | use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+  |                        ^^^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^  ^^^  ^^^^^^^^
+
+warning: unused import: `WindowDecorations`
+  --> src/window.rs:24:75
+   |
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                                                           ^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Proxy`, `globals::registry_queue_init`, and `wl_shm`
+  --> src/window.rs:32:5
+   |
+32 |     globals::registry_queue_init,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                                                             ^^^^^^
+34 |     Connection, QueueHandle, Proxy,
+   |                              ^^^^^
+
+warning: unused import: `calloop_wayland_source::WaylandSource`
+  --> src/window.rs:36:5
+   |
+36 | use calloop_wayland_source::WaylandSource;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `wgpu::util::DeviceExt`
+  --> src/window.rs:38:5
+   |
+38 | use wgpu::util::DeviceExt;
+   |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Breadcrumb`, `Button`, `Canvas`, `Checkbox`, `ColorSelector`, `GraphNode`, `Graph`, `Label`, `MenuBar`, `Paginator`, `ParametersBg`, `Plate`, `ScrollingList`, `Slider`, `Spinbox`, `Splitter`, `Spreadsheet`, `StatusBar`, `Switcher`, `TextLabel`, and `ViewportBg`
+  --> src/window.rs:39:24
+   |
+39 | ...::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |       ^^^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^  ^^^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^           ^^^^^^^^^  ^^^^^  ^^^^^^^^^  ^^^^^^  ^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^^^^^^^^^  ^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `clear_ui::colors`
+  --> src/window.rs:40:5
+   |
+40 | use clear_ui::colors;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Attrs`, `Buffer`, `Cache`, `FontSystem`, `Metrics`, `Resolution`, `SwashCache`, `TextArea`, `TextAtlas`, `TextBounds`, `TextRenderer`, and `Viewport`
+  --> src/window.rs:41:15
+   |
+41 | ...::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
+   |       ^^^^^  ^^^^^^  ^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused imports: `Mat4` and `Vec3`
+  --> src/window.rs:42:12
+   |
+42 | use glam::{Mat4, Vec3};
+   |            ^^^^  ^^^^
+
+warning: unused import: `crate::geometry::*`
+  --> src/window.rs:44:5
+   |
+44 | use crate::geometry::*;
+   |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::project::*`
+  --> src/window.rs:45:5
+   |
+45 | use crate::project::*;
+   |     ^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::render::*`
+  --> src/window.rs:46:5
+   |
+46 | use crate::render::*;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `ShortcutManager` and `Shortcut`
+  --> src/window.rs:47:23
+   |
+47 | use crate::shortcut::{Shortcut, ShortcutManager, Action};
+   |                       ^^^^^^^^  ^^^^^^^^^^^^^^^
+
+warning: unused imports: `DesignSettings`, `NODE_PALETTE_IDX`, and `NodePalette`
+  --> src/window.rs:48:78
+   |
+48 | ...tion, ModifiersState, TouchPhase, DesignSettings, param_display, NodePalette, NODE_PALETTE_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_I...
+   |                                      ^^^^^^^^^^^^^^                 ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^
+
+warning: unused import: `clear_ui::engine::Vertex`
+  --> src/window.rs:49:5
+   |
+49 | use clear_ui::engine::Vertex;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `PathBuf`
+ --> src/project.rs:2:23
+  |
+2 | use std::path::{Path, PathBuf};
+  |                       ^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/project.rs:3:5
+  |
+3 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/render.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::path::Path`
+ --> src/render.rs:2:5
+  |
+2 | use std::path::Path;
+  |     ^^^^^^^^^^^^^^^
+
+warning: unused imports: `CustomEvent`, `HEADER_H`, `SPLITTER1_IDX`, `SPLITTER2_IDX`, and `SPLITTER_W`
+  --> src/render.rs:10:20
+   |
+10 |     State, FsNode, CustomEvent,
+   |                    ^^^^^^^^^^^
+...
+14 |     SPREADSHEET_MENUBAR_IDX, SPLITTER1_IDX, SPLITTER2_IDX,
+   |                              ^^^^^^^^^^^^^  ^^^^^^^^^^^^^
+15 |     MENUBAR_H, STATUS_H, HEADER_H, SPLITTER_W,
+   |                          ^^^^^^^^  ^^^^^^^^^^
+
+warning: unused import: `network_sphere_vertices_with_errors`
+  --> src/render.rs:22:5
+   |
+22 |     network_sphere_vertices_with_errors,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `ParamDef`, `TouchPhase`, and `param_display`
+  --> src/main.rs:53:59
+   |
+53 | ...ion, ModifiersState, TouchPhase, DesignSettings, param_display, FsNode, Project, ProjectViewState, ParamDef};
+   |                         ^^^^^^^^^^                  ^^^^^^^^^^^^^                                     ^^^^^^^^
+
+warning: unused import: `api::start_http_server`
+  --> src/main.rs:55:5
+   |
+55 | use api::start_http_server;
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `project::*`
+  --> src/main.rs:57:5
+   |
+57 | use project::*;
+   |     ^^^^^^^^^^
+
+warning: unused import: `render::*`
+  --> src/main.rs:58:5
+   |
+58 | use render::*;
+   |     ^^^^^^^^^
+
+warning: use of deprecated type alias `wgpu::ImageCopyTexture`: This has been renamed to `TexelCopyTextureInfo`, and will be removed in 25.0.0.
+    --> src/app.rs:5548:23
+     |
+5548 |                 wgpu::ImageCopyTexture {
+     |                       ^^^^^^^^^^^^^^^^
+     |
+     = note: `#[warn(deprecated)]` on by default
+
+warning: use of deprecated type alias `wgpu::ImageCopyTexture`: This has been renamed to `TexelCopyTextureInfo`, and will be removed in 25.0.0.
+    --> src/app.rs:5554:23
+     |
+5554 |                 wgpu::ImageCopyTexture {
+     |                       ^^^^^^^^^^^^^^^^
+
+warning: use of deprecated associated function `opencl3::command_queue::CommandQueue::create`: From CL_VERSION_2_0 use create_command_queue_with_properties
+   --> src/geometry.rs:400:40
+    |
+400 |     let queue = unsafe { CommandQueue::create(&context, device_id, 0) }.ok()?;
+    |                                        ^^^^^^
+
+warning: unused import: `Write`
+ --> src/app.rs:5:41
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                         ^^^^^
+
+warning: unused import: `BufRead`
+ --> src/app.rs:5:15
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |               ^^^^^^^
+
+warning: unused import: `Read`
+ --> src/app.rs:5:35
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                   ^^^^
+
+warning: unused import: `WindowHandler`
+  --> src/app.rs:24:60
+   |
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                                            ^^^^^^^^^^^^^
+
+warning: unused import: `WaylandSurface`
+  --> src/window.rs:27:9
+   |
+27 |         WaylandSurface,
+   |         ^^^^^^^^^^^^^^
+
+warning: unused import: `Read`
+ --> src/window.rs:5:35
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                   ^^^^
+
+warning: unused import: `BufRead`
+ --> src/window.rs:5:15
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |               ^^^^^^^
+
+warning: unused import: `Element`
+ --> src/project.rs:5:32
+  |
+5 | use clear_ui::widget::{Button, Element};
+  |                                ^^^^^^^
+
+warning: unused import: `OutputHandler`
+  --> src/main.rs:16:14
+   |
+16 |     output::{OutputHandler, OutputState},
+   |              ^^^^^^^^^^^^^
+
+warning: unused import: `WaylandSurface`
+  --> src/main.rs:27:9
+   |
+27 |         WaylandSurface,
+   |         ^^^^^^^^^^^^^^
+
+warning: unused import: `Element`
+  --> src/main.rs:39:131
+   |
+39 | ...Bg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spin...
+   |                                                                 ^^^^^^^
+
+warning: unused variable: `surface`
+    --> src/app.rs:2036:13
+     |
+2036 |         let surface = &wgpu_adapter.surface;
+     |             ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_surface`
+     |
+     = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
+
+warning: variable `sp_menub_y` is assigned to, but never used
+    --> src/app.rs:3137:21
+     |
+3137 |                 let mut sp_menub_y = 0.0;
+     |                     ^^^^^^^^^^^^^^
+     |
+     = note: consider using `_sp_menub_y` instead
+
+warning: variable `sp_menub_h` is assigned to, but never used
+    --> src/app.rs:3138:21
+     |
+3138 |                 let mut sp_menub_h = 0.0;
+     |                     ^^^^^^^^^^^^^^
+     |
+     = note: consider using `_sp_menub_h` instead
+
+warning: value assigned to `sp_menub_y` is never read
+    --> src/app.rs:3156:25
+     |
+3156 |                         sp_menub_y = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+     = note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
+
+warning: value assigned to `sp_menub_y` is never read
+    --> src/app.rs:3148:25
+     |
+3148 |                         sp_menub_y = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: value assigned to `sp_menub_h` is never read
+    --> src/app.rs:3157:25
+     |
+3157 |                         sp_menub_h = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: value assigned to `sp_menub_h` is never read
+    --> src/app.rs:3149:25
+     |
+3149 |                         sp_menub_h = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: unused variable: `cx`
+   --> src/window.rs:276:22
+    |
+276 |                 let (cx, cy) = clear_ui::wayland::scale_pointer_pos(event.position, st.scale);
+    |                      ^^ help: if this is intentional, prefix it with an underscore: `_cx`
+
+warning: unused variable: `cy`
+   --> src/window.rs:276:26
+    |
+276 |                 let (cx, cy) = clear_ui::wayland::scale_pointer_pos(event.position, st.scale);
+    |                          ^^ help: if this is intentional, prefix it with an underscore: `_cy`
+
+warning: unused variable: `idx`
+   --> src/geometry.rs:656:17
+    |
+656 |             let idx = *count;
+    |                 ^^^ help: if this is intentional, prefix it with an underscore: `_idx`
+
+warning: unused variable: `idx`
+   --> src/geometry.rs:665:17
+    |
+665 |             let idx = *count;
+    |                 ^^^ help: if this is intentional, prefix it with an underscore: `_idx`
+
+Some errors have detailed explanations: E0425, E0433.
+For more information about an error, try `rustc --explain E0425`.
+warning: `cce-design-interface` (bin "cce-design-interface" test) generated 77 warnings
+error: could not compile `cce-design-interface` (bin "cce-design-interface" test) due to 51 previous errors; 77 warnings emitted
diff --git a/scratch/test_errors2.log b/scratch/test_errors2.log
new file mode 100644
index 0000000..b08d524
--- /dev/null
+++ b/scratch/test_errors2.log
@@ -0,0 +1,1044 @@
+warning: unused import: `KeyEvent`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:3:5
+  |
+3 |     KeyEvent, MouseButton, ElementState, focus, Slider, Event, UiContext,
+  |     ^^^^^^^^
+  |
+  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused import: `Ordering`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/mod.rs:67:38
+   |
+67 | use std::sync::atomic::{AtomicUsize, Ordering};
+   |                                      ^^^^^^^^
+
+warning: unused import: `wl_shm`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:24:61
+   |
+24 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_s...
+   |                                                             ^^^^^^
+
+warning: unused imports: `SwashCache`, `TextAtlas`, and `TextRenderer`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:34:36
+   |
+34 |     Cache, FontSystem, Resolution, SwashCache, TextArea, TextAtlas,
+   |                                    ^^^^^^^^^^            ^^^^^^^^^
+35 |     TextBounds, TextRenderer, Viewport, Buffer, Attrs, Metrics,
+   |                 ^^^^^^^^^^^^
+
+warning: unused imports: `KeyEvent` and `MouseScrollDelta`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/context.rs:2:57
+  |
+2 | ...e, Key, KeyEvent, MouseButton, ElementState, MouseScrollDelta};
+  |            ^^^^^^^^                             ^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::widget::TextBox`
+ --> /home/lsgalante/Dropbox/Clear/clear-ui/src/context.rs:5:5
+  |
+5 | use crate::widget::TextBox;
+  |     ^^^^^^^^^^^^^^^^^^^^^^
+
+warning: variable does not need to be mutable
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/backend/window_runner.rs:925:13
+    |
+925 | ...   let mut adapter = WgpuAdapter::new(display_ptr, surface_ptr, pw, ...
+    |           ----^^^^^^^
+    |           |
+    |           help: remove this `mut`
+    |
+    = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:381:39
+    |
+381 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+    |
+    = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `x`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:440:49
+    |
+440 |             Event::MouseButton { button, state, x, y } => {
+    |                                                 ^ help: try ignoring the field: `x: _`
+
+warning: unused variable: `y`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/json_layout.rs:440:52
+    |
+440 |             Event::MouseButton { button, state, x, y } => {
+    |                                                    ^ help: try ignoring the field: `y: _`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button.rs:98:30
+   |
+98 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/slider.rs:200:75
+    |
+200 | ...Delta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/slider.rs:307:52
+    |
+307 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/spinbox.rs:209:52
+    |
+209 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/color_selector.rs:158:34
+    |
+158 |     fn tick(&mut self, _dt: f32, ctx: &mut UiContext) -> bool {
+    |                                  ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/color_selector.rs:201:52
+    |
+201 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/dropdown.rs:264:52
+    |
+264 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:565:52
+    |
+565 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:918:39
+    |
+918 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/text_box.rs:923:48
+    |
+923 | ...h_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/trackpad.rs:147:91
+    |
+147 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button_strip.rs:216:91
+    |
+216 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: value assigned to `next` is never read
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/input/button_strip.rs:359:24
+    |
+359 |         let mut next = current;
+    |                        ^^^^^^^
+    |
+    = help: maybe it is overwritten before being read?
+    = note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:25:22
+   |
+25 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'sta...
+   |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:26:76
+   |
+26 | ... (dyn Element + 'static)>, ctx: &mut UiContext) { self.parent = paren...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:27:24
+   |
+27 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'stat...
+   |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:28:66
+   |
+28 | ...t (dyn Element + 'static), ctx: &mut UiContext) { self.children.push(...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/container.rs:29:34
+   |
+29 | ... clear_children(&mut self, ctx: &mut UiContext) { self.children.clear...
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:577:23
+    |
+577 |     fn focused(&self, ctx: &UiContext) -> bool {
+    |                       ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1258:22
+     |
+1258 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 's...
+     |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1262:76
+     |
+1262 | ...dyn Element + 'static)>, ctx: &mut UiContext) {
+     |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/menu.rs:1270:23
+     |
+1270 |     fn focused(&self, ctx: &UiContext) -> bool {
+     |                       ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/breadcrumb.rs:60:92
+   |
+60 | ...tState, px: f32, _py: f32, ctx: &mut UiContext) -> bool {
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/spreadsheet.rs:233:33
+    |
+233 |     fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
+    |                                 ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:68:31
+   |
+68 |     fn highlight_color(&self, ctx: &UiContext) -> Option<[f32; 4]> { None }
+   |                               ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:149:52
+    |
+149 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:191:22
+    |
+191 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'st...
+    |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:192:76
+    |
+192 | ...dyn Element + 'static)>, ctx: &mut UiContext) { self.parent = parent; }
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:193:24
+    |
+193 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'sta...
+    |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:194:66
+    |
+194 | ...(dyn Element + 'static), ctx: &mut UiContext) { self.children.push(c...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/scroll_box.rs:195:34
+    |
+195 | ...lear_children(&mut self, ctx: &mut UiContext) { self.children.clear(...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:719:39
+    |
+719 | ...abels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<...
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:967:22
+    |
+967 |     fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'st...
+    |                      ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:971:76
+    |
+971 | ...dyn Element + 'static)>, ctx: &mut UiContext) {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:975:24
+    |
+975 |     fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'sta...
+    |                        ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:984:67
+    |
+984 | ...(dyn Element + 'static), ctx: &mut UiContext) {}
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/container/paginator.rs:985:34
+    |
+985 |     fn clear_children(&mut self, ctx: &mut UiContext) {}
+    |                                  ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/panel.rs:37:30
+   |
+37 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/float3.rs:101:91
+    |
+101 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/float3.rs:152:52
+    |
+152 | ... self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/progress_bar.rs:25:30
+   |
+25 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/list_item.rs:48:30
+   |
+48 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/graph.rs:253:53
+    |
+253 | ... self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+   --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/graph.rs:267:91
+    |
+267 | ...State, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+    |                             ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/usage_bar.rs:35:30
+   |
+35 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/layout_preview.rs:55:30
+   |
+55 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/font_preview.rs:26:30
+   |
+26 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/info_box.rs:25:30
+   |
+25 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: unused variable: `ctx`
+  --> /home/lsgalante/Dropbox/Clear/clear-ui/src/widget/display/status_dot.rs:32:30
+   |
+32 |     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, ...
+   |                              ^^^ help: if this is intentional, prefix it with an underscore: `_ctx`
+
+warning: variable does not need to be mutable
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/layout.rs:1918:31
+     |
+1918 |     pub fn with_section_count(mut self, count: usize) -> Self {
+     |                               ----^^^^
+     |                               |
+     |                               help: remove this `mut`
+
+warning: field `min_col_width` is never read
+    --> /home/lsgalante/Dropbox/Clear/clear-ui/src/layout.rs:1793:5
+     |
+1791 | pub struct GridLayout {
+     |            ---------- field in this struct
+1792 |     grid: Option<Grid>,
+1793 |     min_col_width: f32,
+     |     ^^^^^^^^^^^^^
+     |
+     = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
+
+warning: `clear-ui` (lib) generated 61 warnings (run `cargo fix --lib -p clear-ui` to apply 59 suggestions)
+   Compiling cce-design-interface v0.1.0 (/home/lsgalante/Dropbox/Clear/cce-design-interface)
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:825:22
+    |
+825 |         let ctrl_g = Shortcut::parse("Ctrl+g").unwrap();
+    |                      ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:833:23
+    |
+833 |         let complex = Shortcut::parse("Ctrl+Shift+Alt+Logo+s").unwrap();
+    |                       ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:841:22
+    |
+841 |         let tab_sc = Shortcut::parse("Tab").unwrap();
+    |                      ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `Shortcut` in this scope
+   --> src/main.rs:845:23
+    |
+845 |         let case_sc = Shortcut::parse("cTrL+sHiFt+ArrowDown").unwrap();
+    |                       ^^^^^^^^ use of undeclared type `Shortcut`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::Shortcut;
+    |
+
+error[E0433]: cannot find type `ShortcutManager` in this scope
+   --> src/main.rs:851:23
+    |
+851 |         let mut mgr = ShortcutManager::new();
+    |                       ^^^^^^^^^^^^^^^ use of undeclared type `ShortcutManager`
+    |
+help: consider importing this struct
+    |
+392 +     use crate::shortcut::ShortcutManager;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:852:32
+    |
+852 |         mgr.register("Ctrl+g", Action::ToggleGrid).unwrap();
+    |                                ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+852 -         mgr.register("Ctrl+g", Action::ToggleGrid).unwrap();
+852 +         mgr.register("Ctrl+g", Option::ToggleGrid).unwrap();
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:853:27
+    |
+853 |         mgr.register("`", Action::ToggleSpreadsheet).unwrap();
+    |                           ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+853 -         mgr.register("`", Action::ToggleSpreadsheet).unwrap();
+853 +         mgr.register("`", Option::ToggleSpreadsheet).unwrap();
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:858:63
+    |
+858 |         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Action::ToggleGrid));
+    |                                                               ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+858 -         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Action::ToggleGrid));
+858 +         assert_eq!(mgr.match_action(&mods_ctrl, &key_g), Some(Option::ToggleGrid));
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+error[E0433]: cannot find type `Action` in this scope
+   --> src/main.rs:867:66
+    |
+867 |         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Action::ToggleSpreadsheet));
+    |                                                                  ^^^^^^ use of undeclared type `Action`
+    |
+help: an enum with a similar name exists
+    |
+867 -         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Action::ToggleSpreadsheet));
+867 +         assert_eq!(mgr.match_action(&mods_none, &key_tick), Some(Option::ToggleSpreadsheet));
+    |
+help: consider importing this enum
+    |
+392 +     use crate::shortcut::Action;
+    |
+
+warning: unused import: `std::time::Instant`
+ --> src/main.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+  |
+  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+
+warning: unused imports: `Deserialize` and `Serialize`
+ --> src/main.rs:7:13
+  |
+7 | use serde::{Deserialize, Serialize};
+  |             ^^^^^^^^^^^  ^^^^^^^^^
+
+warning: unused imports: `ElementState`, `KeyEvent`, `MouseButton`, and `MouseScrollDelta`
+ --> src/main.rs:9:24
+  |
+9 | use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+  |                        ^^^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused imports: `Capability`, `CompositorHandler`, `CursorIcon`, `PointerHandler`, `ProvidesRegistryState`, `SeatHandler`, `ShmHandler`, `ThemeSpec`, `ThemedPointer`, `Window as XdgWindow`, `WindowConfigure`, `WindowDecorations`, `WindowHandler`, `delegate_compositor`, `delegate_keyboard`, `delegate_output`, `delegate_pointer`, `delegate_registry`, `delegate_seat`, `delegate_shm`, `delegate_xdg_shell`, `delegate_xdg_window`, and `keyboard::KeyboardHandler`
+  --> src/main.rs:12:18
+   |
+12 |     compositor::{CompositorHandler, CompositorState},
+   |                  ^^^^^^^^^^^^^^^^^
+13 |     delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+   |     ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+14 |     delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
+15 |     registry::{ProvidesRegistryState, RegistryState},
+   |                ^^^^^^^^^^^^^^^^^^^^^
+...
+18 |         keyboard::KeyboardHandler,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+19 |         pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
+   |                   ^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^
+20 |         Capability, SeatHandler, SeatState,
+   |         ^^^^^^^^^^  ^^^^^^^^^^^
+...
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                      ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+...
+29 |     shm::{Shm, ShmHandler},
+   |                ^^^^^^^^^^
+
+warning: unused imports: `Proxy`, `QueueHandle`, `wl_keyboard`, `wl_output`, `wl_pointer`, `wl_seat`, `wl_shm`, and `wl_surface`
+  --> src/main.rs:33:16
+   |
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^  ^^^^^^^^^^
+34 |     Connection, QueueHandle, Proxy,
+   |                 ^^^^^^^^^^^  ^^^^^
+
+warning: unused import: `wgpu::util::DeviceExt`
+  --> src/main.rs:38:5
+   |
+38 | use wgpu::util::DeviceExt;
+   |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Breadcrumb`, `Button`, `Canvas`, `Checkbox`, `ColorSelector`, `GraphNode`, `Graph`, `Label`, `MenuBar`, `Paginator`, `ParametersBg`, `Plate`, `ScrollingList`, `Slider`, `Spinbox`, `Splitter`, `Spreadsheet`, `StatusBar`, `Switcher`, `TextLabel`, and `ViewportBg`
+  --> src/main.rs:39:24
+   |
+39 | ...::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |       ^^^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^  ^^^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^           ^^^^^^^^^  ^^^^^  ^^^^^^^^^  ^^^^^^  ^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^^^^^^^^^  ^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `clear_ui::colors`
+  --> src/main.rs:40:5
+   |
+40 | use clear_ui::colors;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Attrs`, `Buffer`, `Cache`, `FontSystem`, `Metrics`, `Resolution`, `SwashCache`, `TextArea`, `TextAtlas`, `TextBounds`, `TextRenderer`, and `Viewport`
+  --> src/main.rs:41:15
+   |
+41 | ...::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
+   |       ^^^^^  ^^^^^^  ^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `Mat4`
+  --> src/main.rs:42:12
+   |
+42 | use glam::{Mat4, Vec3};
+   |            ^^^^
+
+warning: unused import: `std::net::TcpListener`
+ --> src/app.rs:4:5
+  |
+4 | use std::net::TcpListener;
+  |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `BufReader`
+ --> src/app.rs:5:24
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                        ^^^^^^^^^
+
+warning: unused imports: `Capability`, `CompositorHandler`, `CursorIcon`, `OutputHandler`, `OutputState`, `PointerHandler`, `ProvidesRegistryState`, `RegistryState`, `SeatHandler`, `SeatState`, `ShmHandler`, `Shm`, `ThemeSpec`, `ThemedPointer`, `WindowConfigure`, `delegate_compositor`, `delegate_keyboard`, `delegate_output`, `delegate_pointer`, `delegate_registry`, `delegate_seat`, `delegate_shm`, `delegate_xdg_shell`, `delegate_xdg_window`, and `keyboard::KeyboardHandler`
+  --> src/app.rs:12:18
+   |
+12 |     compositor::{CompositorHandler, CompositorState},
+   |                  ^^^^^^^^^^^^^^^^^
+13 |     delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+   |     ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^
+14 |     delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
+15 |     registry::{ProvidesRegistryState, RegistryState},
+   |                ^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^
+16 |     output::{OutputHandler, OutputState},
+   |              ^^^^^^^^^^^^^  ^^^^^^^^^^^
+17 |     seat::{
+18 |         keyboard::KeyboardHandler,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+19 |         pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
+   |                   ^^^^^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^
+20 |         Capability, SeatHandler, SeatState,
+   |         ^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^
+...
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                           ^^^^^^^^^^^^^^^
+...
+29 |     shm::{Shm, ShmHandler},
+   |           ^^^  ^^^^^^^^^^
+
+warning: unused imports: `globals::registry_queue_init`, `wl_keyboard`, `wl_output`, `wl_pointer`, `wl_seat`, and `wl_shm`
+  --> src/app.rs:32:5
+   |
+32 |     globals::registry_queue_init,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^
+
+warning: unused import: `calloop_wayland_source::WaylandSource`
+  --> src/app.rs:36:5
+   |
+36 | use calloop_wayland_source::WaylandSource;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `Switcher`
+  --> src/app.rs:39:241
+   |
+39 | ...kbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |                                                                ^^^^^^^^
+
+warning: unused imports: `SwashCache`, `TextArea`, and `TextBounds`
+  --> src/app.rs:42:70
+   |
+42 | use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, View...
+   |                                                                      ^^^^^^^^^^  ^^^^^^^^             ^^^^^^^^^^
+
+warning: unused import: `crate::project::*`
+  --> src/app.rs:46:5
+   |
+46 | use crate::project::*;
+   |     ^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::render::*`
+  --> src/app.rs:47:5
+   |
+47 | use crate::render::*;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused import: `Shortcut`
+  --> src/app.rs:48:23
+   |
+48 | use crate::shortcut::{Shortcut, ShortcutManager, Action};
+   |                       ^^^^^^^^
+
+warning: unused import: `LocalPosition`
+  --> src/app.rs:51:44
+   |
+51 | use crate::window::{AppState, WindowEvent, LocalPosition};
+   |                                            ^^^^^^^^^^^^^
+
+warning: unused import: `clear_ui::engine::Vertex`
+ --> src/graphics.rs:1:5
+  |
+1 | use clear_ui::engine::Vertex;
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/window.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::fs`
+ --> src/window.rs:2:5
+  |
+2 | use std::fs;
+  |     ^^^^^^^
+
+warning: unused import: `std::net::TcpListener`
+ --> src/window.rs:4:5
+  |
+4 | use std::net::TcpListener;
+  |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `BufReader` and `Write`
+ --> src/window.rs:5:24
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                        ^^^^^^^^^        ^^^^^
+
+warning: unused imports: `Deserialize` and `Serialize`
+ --> src/window.rs:7:13
+  |
+7 | use serde::{Deserialize, Serialize};
+  |             ^^^^^^^^^^^  ^^^^^^^^^
+
+warning: unused imports: `ElementState`, `KeyEvent`, `Key`, `MouseButton`, `MouseScrollDelta`, and `NamedKey`
+ --> src/window.rs:9:24
+  |
+9 | use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+  |                        ^^^^^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^^^^^^  ^^^  ^^^^^^^^
+
+warning: unused import: `WindowDecorations`
+  --> src/window.rs:24:75
+   |
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                                                           ^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Proxy`, `globals::registry_queue_init`, and `wl_shm`
+  --> src/window.rs:32:5
+   |
+32 |     globals::registry_queue_init,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+33 |     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+   |                                                             ^^^^^^
+34 |     Connection, QueueHandle, Proxy,
+   |                              ^^^^^
+
+warning: unused import: `calloop_wayland_source::WaylandSource`
+  --> src/window.rs:36:5
+   |
+36 | use calloop_wayland_source::WaylandSource;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `wgpu::util::DeviceExt`
+  --> src/window.rs:38:5
+   |
+38 | use wgpu::util::DeviceExt;
+   |     ^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Breadcrumb`, `Button`, `Canvas`, `Checkbox`, `ColorSelector`, `GraphNode`, `Graph`, `Label`, `MenuBar`, `Paginator`, `ParametersBg`, `Plate`, `ScrollingList`, `Slider`, `Spinbox`, `Splitter`, `Spreadsheet`, `StatusBar`, `Switcher`, `TextLabel`, and `ViewportBg`
+  --> src/window.rs:39:24
+   |
+39 | ...::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+   |       ^^^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^  ^^^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^           ^^^^^^^^^  ^^^^^  ^^^^^^^^^  ^^^^^^  ^^^^^^^^  ^^^^^^  ^^^^^^^  ^^^^^^^^^^^^^  ^^^^^  ^^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused import: `clear_ui::colors`
+  --> src/window.rs:40:5
+   |
+40 | use clear_ui::colors;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `Attrs`, `Buffer`, `Cache`, `FontSystem`, `Metrics`, `Resolution`, `SwashCache`, `TextArea`, `TextAtlas`, `TextBounds`, `TextRenderer`, and `Viewport`
+  --> src/window.rs:41:15
+   |
+41 | ...::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
+   |       ^^^^^  ^^^^^^  ^^^^^  ^^^^^^^^^^  ^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^^^  ^^^^^^^^
+
+warning: unused imports: `Mat4` and `Vec3`
+  --> src/window.rs:42:12
+   |
+42 | use glam::{Mat4, Vec3};
+   |            ^^^^  ^^^^
+
+warning: unused import: `crate::geometry::*`
+  --> src/window.rs:44:5
+   |
+44 | use crate::geometry::*;
+   |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::project::*`
+  --> src/window.rs:45:5
+   |
+45 | use crate::project::*;
+   |     ^^^^^^^^^^^^^^^^^
+
+warning: unused import: `crate::render::*`
+  --> src/window.rs:46:5
+   |
+46 | use crate::render::*;
+   |     ^^^^^^^^^^^^^^^^
+
+warning: unused imports: `ShortcutManager` and `Shortcut`
+  --> src/window.rs:47:23
+   |
+47 | use crate::shortcut::{Shortcut, ShortcutManager, Action};
+   |                       ^^^^^^^^  ^^^^^^^^^^^^^^^
+
+warning: unused imports: `DesignSettings`, `NODE_PALETTE_IDX`, and `NodePalette`
+  --> src/window.rs:48:78
+   |
+48 | ...tion, ModifiersState, TouchPhase, DesignSettings, param_display, NodePalette, NODE_PALETTE_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_I...
+   |                                      ^^^^^^^^^^^^^^                 ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^
+
+warning: unused import: `clear_ui::engine::Vertex`
+  --> src/window.rs:49:5
+   |
+49 | use clear_ui::engine::Vertex;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `PathBuf`
+ --> src/project.rs:2:23
+  |
+2 | use std::path::{Path, PathBuf};
+  |                       ^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/project.rs:3:5
+  |
+3 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::time::Instant`
+ --> src/render.rs:1:5
+  |
+1 | use std::time::Instant;
+  |     ^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `std::path::Path`
+ --> src/render.rs:2:5
+  |
+2 | use std::path::Path;
+  |     ^^^^^^^^^^^^^^^
+
+warning: unused imports: `CustomEvent`, `HEADER_H`, `SPLITTER1_IDX`, `SPLITTER2_IDX`, and `SPLITTER_W`
+  --> src/render.rs:10:20
+   |
+10 |     State, FsNode, CustomEvent,
+   |                    ^^^^^^^^^^^
+...
+14 |     SPREADSHEET_MENUBAR_IDX, SPLITTER1_IDX, SPLITTER2_IDX,
+   |                              ^^^^^^^^^^^^^  ^^^^^^^^^^^^^
+15 |     MENUBAR_H, STATUS_H, HEADER_H, SPLITTER_W,
+   |                          ^^^^^^^^  ^^^^^^^^^^
+
+warning: unused import: `network_sphere_vertices_with_errors`
+  --> src/render.rs:22:5
+   |
+22 |     network_sphere_vertices_with_errors,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused imports: `ParamDef`, `TouchPhase`, and `param_display`
+  --> src/main.rs:53:59
+   |
+53 | ...ion, ModifiersState, TouchPhase, DesignSettings, param_display, FsNode, Project, ProjectViewState, ParamDef};
+   |                         ^^^^^^^^^^                  ^^^^^^^^^^^^^                                     ^^^^^^^^
+
+warning: unused import: `api::start_http_server`
+  --> src/main.rs:55:5
+   |
+55 | use api::start_http_server;
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused import: `project::*`
+  --> src/main.rs:57:5
+   |
+57 | use project::*;
+   |     ^^^^^^^^^^
+
+warning: unused import: `render::*`
+  --> src/main.rs:58:5
+   |
+58 | use render::*;
+   |     ^^^^^^^^^
+
+warning: use of deprecated type alias `wgpu::ImageCopyTexture`: This has been renamed to `TexelCopyTextureInfo`, and will be removed in 25.0.0.
+    --> src/app.rs:5548:23
+     |
+5548 |                 wgpu::ImageCopyTexture {
+     |                       ^^^^^^^^^^^^^^^^
+     |
+     = note: `#[warn(deprecated)]` on by default
+
+warning: use of deprecated type alias `wgpu::ImageCopyTexture`: This has been renamed to `TexelCopyTextureInfo`, and will be removed in 25.0.0.
+    --> src/app.rs:5554:23
+     |
+5554 |                 wgpu::ImageCopyTexture {
+     |                       ^^^^^^^^^^^^^^^^
+
+warning: use of deprecated associated function `opencl3::command_queue::CommandQueue::create`: From CL_VERSION_2_0 use create_command_queue_with_properties
+   --> src/geometry.rs:400:40
+    |
+400 |     let queue = unsafe { CommandQueue::create(&context, device_id, 0) }.ok()?;
+    |                                        ^^^^^^
+
+warning: unused import: `Write`
+ --> src/app.rs:5:41
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                         ^^^^^
+
+warning: unused import: `BufRead`
+ --> src/app.rs:5:15
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |               ^^^^^^^
+
+warning: unused import: `Read`
+ --> src/app.rs:5:35
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                   ^^^^
+
+warning: unused import: `WindowHandler`
+  --> src/app.rs:24:60
+   |
+24 |             window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+   |                                                            ^^^^^^^^^^^^^
+
+warning: unused import: `WaylandSurface`
+  --> src/window.rs:27:9
+   |
+27 |         WaylandSurface,
+   |         ^^^^^^^^^^^^^^
+
+warning: unused import: `Read`
+ --> src/window.rs:5:35
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |                                   ^^^^
+
+warning: unused import: `BufRead`
+ --> src/window.rs:5:15
+  |
+5 | use std::io::{BufRead, BufReader, Read, Write};
+  |               ^^^^^^^
+
+warning: unused import: `Element`
+ --> src/project.rs:5:32
+  |
+5 | use clear_ui::widget::{Button, Element};
+  |                                ^^^^^^^
+
+warning: unused import: `OutputHandler`
+  --> src/main.rs:16:14
+   |
+16 |     output::{OutputHandler, OutputState},
+   |              ^^^^^^^^^^^^^
+
+warning: unused import: `WaylandSurface`
+  --> src/main.rs:27:9
+   |
+27 |         WaylandSurface,
+   |         ^^^^^^^^^^^^^^
+
+warning: unused import: `Element`
+  --> src/main.rs:39:131
+   |
+39 | ...Bg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spin...
+   |                                                                 ^^^^^^^
+
+warning: unused variable: `surface`
+    --> src/app.rs:2036:13
+     |
+2036 |         let surface = &wgpu_adapter.surface;
+     |             ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_surface`
+     |
+     = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
+
+warning: variable `sp_menub_y` is assigned to, but never used
+    --> src/app.rs:3137:21
+     |
+3137 |                 let mut sp_menub_y = 0.0;
+     |                     ^^^^^^^^^^^^^^
+     |
+     = note: consider using `_sp_menub_y` instead
+
+warning: variable `sp_menub_h` is assigned to, but never used
+    --> src/app.rs:3138:21
+     |
+3138 |                 let mut sp_menub_h = 0.0;
+     |                     ^^^^^^^^^^^^^^
+     |
+     = note: consider using `_sp_menub_h` instead
+
+warning: value assigned to `sp_menub_y` is never read
+    --> src/app.rs:3156:25
+     |
+3156 |                         sp_menub_y = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+     = note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default
+
+warning: value assigned to `sp_menub_y` is never read
+    --> src/app.rs:3148:25
+     |
+3148 |                         sp_menub_y = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: value assigned to `sp_menub_h` is never read
+    --> src/app.rs:3157:25
+     |
+3157 |                         sp_menub_h = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: value assigned to `sp_menub_h` is never read
+    --> src/app.rs:3149:25
+     |
+3149 |                         sp_menub_h = 0.0;
+     |                         ^^^^^^^^^^^^^^^^
+     |
+     = help: maybe it is overwritten before being read?
+
+warning: unused variable: `cx`
+   --> src/window.rs:276:22
+    |
+276 |                 let (cx, cy) = clear_ui::wayland::scale_pointer_pos(event.position, st.scale);
+    |                      ^^ help: if this is intentional, prefix it with an underscore: `_cx`
+
+warning: unused variable: `cy`
+   --> src/window.rs:276:26
+    |
+276 |                 let (cx, cy) = clear_ui::wayland::scale_pointer_pos(event.position, st.scale);
+    |                          ^^ help: if this is intentional, prefix it with an underscore: `_cy`
+
+warning: unused variable: `idx`
+   --> src/geometry.rs:656:17
+    |
+656 |             let idx = *count;
+    |                 ^^^ help: if this is intentional, prefix it with an underscore: `_idx`
+
+warning: unused variable: `idx`
+   --> src/geometry.rs:665:17
+    |
+665 |             let idx = *count;
+    |                 ^^^ help: if this is intentional, prefix it with an underscore: `_idx`
+
+For more information about this error, try `rustc --explain E0433`.
+warning: `cce-design-interface` (bin "cce-design-interface" test) generated 77 warnings
+error: could not compile `cce-design-interface` (bin "cce-design-interface" test) due to 9 previous errors; 77 warnings emitted
diff --git a/scratch/test_selection.py b/scratch/test_selection.py
new file mode 100644
index 0000000..875770e
--- /dev/null
+++ b/scratch/test_selection.py
@@ -0,0 +1,29 @@
+import subprocess
+import time
+
+def run_clearctl(cmd_str):
+    full_cmd = f"env WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 /home/lsgalante/.local/bin/clearctl {cmd_str}"
+    res = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
+    return res.stdout.strip()
+
+def click_at(x, y):
+    print(f"Moving to {x}, {y}...")
+    run_clearctl(f"pointer-move-to {x} {y}")
+    time.sleep(0.2)
+    print(f"Clicking at {x}, {y}...")
+    run_clearctl("pointer-click left")
+    time.sleep(0.3)
+
+# Focus the design interface window
+run_clearctl("focus-window cce-design-interface")
+time.sleep(0.5)
+
+# Click on Box 1 (x=238, y=389)
+click_at(238, 389)
+subprocess.run("env WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 grim /home/lsgalante/.gemini/antigravity/brain/ab8b2bd9-c7ba-4730-96bb-8766ee711f3a/screenshot_click_box.png", shell=True)
+
+# Click on Scatter 1 (x=238, y=469)
+click_at(238, 469)
+subprocess.run("env WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 grim /home/lsgalante/.gemini/antigravity/brain/ab8b2bd9-c7ba-4730-96bb-8766ee711f3a/screenshot_click_scatter.png", shell=True)
+
+print("Done.")
diff --git a/src/app.rs b/src/app.rs
index 0a7a4b7..9f31eb6 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -135,6 +135,9 @@ pub fn generate_node_id() -> String {
     format!("node_{:x}_{:x}", now, count)
 }
 
+fn default_node_inputs() -> usize { 1 }
+fn default_node_outputs() -> usize { 1 }
+
 #[derive(Clone, Deserialize, Serialize)]
 pub struct FsNode {
     #[serde(default = "generate_node_id")]
@@ -152,6 +155,10 @@ pub struct FsNode {
     pub geometry_visible: bool,
     #[serde(default = "default_node_position")]
     pub position: (f32, f32),
+    #[serde(default = "default_node_inputs")]
+    pub inputs: usize,
+    #[serde(default = "default_node_outputs")]
+    pub outputs: usize,
 }
 
 fn default_node_type() -> String { "node".to_string() }
@@ -237,6 +244,8 @@ pub fn param_display(params: &[ParamDef]) -> Vec<(String, String, String)> {
             let max = p.max.unwrap_or(10000.0) as i32;
             let step = p.step.unwrap_or(1.0) as i32;
             format!("spinbox:{}:{}:{}", min, max, step)
+        } else if p.param_type == "choice" {
+            format!("choice:{}", p.options.join(","))
         } else {
             p.param_type.clone()
         };
@@ -245,23 +254,9 @@ pub fn param_display(params: &[ParamDef]) -> Vec<(String, String, String)> {
 }
 
 pub fn flatten_node_templates(root: &FsNode) -> Vec<NodeTemplate> {
-    fn visit(node: &FsNode, path: &mut Vec<String>, out: &mut Vec<NodeTemplate>) {
-        if !node.name.is_empty() {
-            path.push(node.name.clone());
-            out.push(NodeTemplate { label: path.join(" / "), node: node.clone() });
-        }
-        for child in &node.children {
-            visit(child, path, out);
-        }
-        if !node.name.is_empty() {
-            path.pop();
-        }
-    }
-
     let mut out = Vec::new();
-    let mut path = Vec::new();
     for child in &root.children {
-        visit(child, &mut path, &mut out);
+        out.push(NodeTemplate { label: child.name.clone(), node: child.clone() });
     }
     out
 }
@@ -276,12 +271,46 @@ pub fn load_fs_tree() -> FsNode {
             .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("json"))
             .collect();
         paths.sort();
+        
+        let mut raw_nodes = Vec::new();
         for path in paths {
             if let Ok(content) = fs::read_to_string(&path) {
                 if let Ok(node) = serde_json::from_str::<FsNode>(&content) {
-                    children.push(node);
+                    raw_nodes.push(node);
+                }
+            }
+        }
+
+        for mut node in raw_nodes.clone() {
+            let mut resolved_children = Vec::new();
+            for child in &node.children {
+                let base_template = raw_nodes.iter().find(|t| {
+                    t.node_type == child.node_type || t.name.to_lowercase() == child.node_type.to_lowercase()
+                });
+                if let Some(base) = base_template {
+                    let mut resolved_child = base.clone();
+                    resolved_child.id = child.id.clone();
+                    if resolved_child.id.is_empty() || resolved_child.id == "node_0_0" || resolved_child.id.starts_with("node_") {
+                        resolved_child.id = generate_node_id();
+                    }
+                    resolved_child.name = child.name.clone();
+                    resolved_child.position = child.position;
+                    // Merge parameters
+                    for override_p in &child.params {
+                        if let Some(base_p) = resolved_child.params.iter_mut().find(|p| p.name == override_p.name) {
+                            base_p.default = override_p.default.clone();
+                        }
+                    }
+                    resolved_children.push(resolved_child);
+                } else {
+                    panic!(
+                        "Node template of type '{}' not found for child '{}' in template '{}'",
+                        child.node_type, child.name, node.name
+                    );
                 }
             }
+            node.children = resolved_children;
+            children.push(node);
         }
     }
     FsNode {
@@ -292,6 +321,8 @@ pub fn load_fs_tree() -> FsNode {
         params: vec![],
         geometry_visible: true,
         position: (0.0, 0.0),
+        inputs: 0,
+        outputs: 0,
     }
 }
 
@@ -302,13 +333,13 @@ fn default_node_color() -> [f32; 3] { [0.10, 0.45, 0.70] }
 fn default_grid_color() -> [f32; 3] { [0.35, 0.35, 0.40] }
 fn default_uniform_background() -> bool { false }
 fn default_network_opacity() -> f32 { 0.95 }
+fn default_cell_opacity() -> f32 { 0.95 }
+fn default_gap_opacity() -> f32 { 0.95 }
 fn default_cell_color() -> [f32; 3] { [0.13, 0.13, 0.16] }
 fn default_gap_color() -> [f32; 3] { [0.07, 0.07, 0.09] }
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
 pub struct DesignSettings {
-    pub grid_snap_enabled: bool,
-    pub network_grid_enabled: bool,
     pub grid_size_x: f32,
     pub grid_size_y: f32,
     pub skipped_row_h: f32,
@@ -329,10 +360,6 @@ pub struct DesignSettings {
     pub node_color: [f32; 3],
     #[serde(default = "default_grid_color")]
     pub grid_color: [f32; 3],
-    #[serde(default = "default_uniform_background")]
-    pub uniform_background: bool,
-    #[serde(default = "default_network_opacity")]
-    pub network_opacity: f32,
     #[serde(default = "default_cell_color")]
     pub cell_color: [f32; 3],
     #[serde(default = "default_gap_color")]
@@ -342,8 +369,6 @@ pub struct DesignSettings {
 impl Default for DesignSettings {
     fn default() -> Self {
         Self {
-            grid_snap_enabled: true,
-            network_grid_enabled: true,
             grid_size_x: 80.0,
             grid_size_y: 40.0,
             skipped_row_h: 20.0,
@@ -359,8 +384,6 @@ impl Default for DesignSettings {
             camera_pivot_size: 1.0,
             node_color: default_node_color(),
             grid_color: default_grid_color(),
-            uniform_background: false,
-            network_opacity: 0.95,
             cell_color: default_cell_color(),
             gap_color: default_gap_color(),
         }
@@ -380,11 +403,10 @@ impl DesignSettings {
     fn load() -> Self {
         let path = Self::file_path();
         if let Ok(content) = fs::read_to_string(&path) {
-            if let Ok(settings) = serde_json::from_str::<Self>(&content) {
-                return settings;
-            }
+            serde_json::from_str::<Self>(&content).unwrap_or_else(|_| Self::default())
+        } else {
+            Self::default()
         }
-        Self::default()
     }
 
     fn save(&self) {
@@ -510,7 +532,7 @@ pub fn make_text_buffer_with_font(font_system: &mut FontSystem, text: &str, size
     let family_name = font.map(|f| clear_ui::layout::parse_font_string(f).0);
     if let Some(ref name) = family_name {
         let family = match name.as_str() {
-            "monospace" => glyphon::Family::Monospace,
+            "monospace" => glyphon::Family::Name(clear_ui::layout::get_system_monospace_font()),
             "sans-serif" => glyphon::Family::SansSerif,
             "serif" => glyphon::Family::Serif,
             _ => glyphon::Family::Name(name),
@@ -710,6 +732,7 @@ pub struct State {
     pub fs_root: FsNode,
     pub node_templates: Vec<NodeTemplate>,
     pub current_path: Vec<usize>,
+    pub node_clipboard: Option<FsNode>,
     pub last_click: Option<(Instant, usize)>,
     pub last_frame: Instant,
 
@@ -809,6 +832,8 @@ pub struct State {
     pub active_menu_cloud_idx: Option<(usize, usize)>,
     pub uniform_background: bool,
     pub network_opacity: f32,
+    pub cell_opacity: f32,
+    pub gap_opacity: f32,
     pub last_design_mod_time: Option<std::time::SystemTime>,
     pub last_config_mod_time: Option<std::time::SystemTime>,
     pub floating_network_layout: (f32, f32, f32, f32),
@@ -830,6 +855,9 @@ pub struct State {
     pub recent_files_buttons: Vec<Button>,
     pub text_buffer_cache: std::collections::HashMap<(String, u32, Option<String>), Buffer>,
     pub viewport_dirty: bool,
+    pub text_dirty: bool,
+    pub last_popover_rects: Vec<(f32, f32, f32, f32)>,
+    pub last_status_text: String,
     pub last_viewport_camera_pos: Vec3,
     pub last_viewport_camera_rx: f32,
     pub last_viewport_camera_ry: f32,
@@ -888,6 +916,10 @@ impl State {
         false
     }
 
+    pub fn palette(&self) -> &NodePalette {
+        self.widgets[NODE_PALETTE_IDX].as_any().downcast_ref::<NodePalette>().expect("not a NodePalette")
+    }
+
     pub fn palette_mut(&mut self) -> &mut NodePalette {
         self.widgets[NODE_PALETTE_IDX].as_any_mut().downcast_mut::<NodePalette>().expect("not a NodePalette")
     }
@@ -999,8 +1031,6 @@ impl State {
 
     pub fn save_settings(&mut self) {
         let settings = DesignSettings {
-            grid_snap_enabled: self.grid_snap_enabled,
-            network_grid_enabled: self.network_grid_visible,
             grid_size_x: self.grid_size_x,
             grid_size_y: self.grid_size_y,
             skipped_row_h: self.skipped_row_h,
@@ -1018,8 +1048,6 @@ impl State {
             grid_thickness: self.grid_thickness,
             show_camera_pivot_enabled: self.show_camera_pivot,
             camera_pivot_size: self.camera_pivot_size,
-            uniform_background: self.uniform_background,
-            network_opacity: self.network_opacity,
         };
         settings.save();
         self.last_design_mod_time = {
@@ -1028,238 +1056,7 @@ impl State {
         };
     }
 
-    pub fn sync_settings_from_paginator(&mut self) {
-        let active_menubar = self.focused_pane;
-        let page_idx = if active_menubar == LEFT_MENUBAR_IDX {
-            3
-        } else if active_menubar == RIGHT_MENUBAR_IDX {
-            4
-        } else {
-            return;
-        };
-
-        if page_idx >= self.paginator_page_widgets.len() {
-            return;
-        }
-
-        let mut left_values = None;
-        let mut right_values = None;
-
-        {
-            let widgets = &self.paginator_page_widgets[page_idx];
-            if widgets.is_empty() {
-                return;
-            }
-
-            if active_menubar == LEFT_MENUBAR_IDX {
-                if widgets.len() >= 11 {
-                    let snap = widgets[0].value();
-                    let vis = widgets[1].value();
-                    let gx = widgets[2].value();
-                    let gy = widgets[3].value();
-                    let srh = widgets[4].value();
-                    let scw = widgets[5].value();
-                    let uni = widgets[6].value();
-                    let op = widgets[7].value();
-                    let node_col = widgets[8].color_u8();
-                    let cell_col = widgets[9].color_u8();
-                    let gap_col = widgets[10].color_u8();
-                    left_values = Some((snap, vis, gx, gy, srh, scw, uni, op, node_col, cell_col, gap_col));
-                }
-            } else if active_menubar == RIGHT_MENUBAR_IDX {
-                if widgets.len() >= 13 {
-                    let sg = widgets[0].value();
-                    let sc = widgets[1].value();
-                    let so = widgets[2].value();
-                    let cp = widgets[3].value();
-                    let gt = widgets[4].value();
-                    let os = widgets[5].value();
-                    let cps = widgets[6].value();
-                    let bgr = widgets[7].value();
-                    let bgg = widgets[8].value();
-                    let bgb = widgets[9].value();
-                    let gcr = widgets[10].value();
-                    let gcg = widgets[11].value();
-                    let gcb = widgets[12].value();
-                    right_values = Some((sg, sc, so, cp, gt, os, cps, bgr, bgg, bgb, gcr, gcg, gcb));
-                }
-            }
-        }
-
-        let mut changed = false;
 
-        if let Some((snap, vis, gx, gy, srh, scw, uni, op, node_col, cell_col, gap_col)) = left_values {
-            // LEFT pane Settings
-            // 0: Snap to Grid (Checkbox)
-            let snap = snap == 1;
-            if self.grid_snap_enabled != snap {
-                self.grid_snap_enabled = snap;
-                changed = true;
-            }
-            // 1: Grid Visible (Checkbox)
-            let vis = vis == 1;
-            if self.network_grid_visible != vis {
-                self.network_grid_visible = vis;
-                changed = true;
-            }
-            // 2: Grid X (Spinbox)
-            let gx = gx as f32;
-            if self.grid_size_x != gx {
-                self.grid_size_x = gx;
-                changed = true;
-            }
-            // 3: Grid Y (Spinbox)
-            let gy = gy as f32;
-            if self.grid_size_y != gy {
-                self.grid_size_y = gy;
-                changed = true;
-            }
-            // 4: Skipped Row H (Spinbox)
-            let srh = srh as f32;
-            if self.skipped_row_h != srh {
-                self.skipped_row_h = srh;
-                changed = true;
-            }
-            // 5: Skipped Col W (Spinbox)
-            let scw = scw as f32;
-            if self.skipped_col_w != scw {
-                self.skipped_col_w = scw;
-                changed = true;
-            }
-            // 6: Uniform Background (Checkbox)
-            let uni = uni == 1;
-            if self.uniform_background != uni {
-                self.uniform_background = uni;
-                changed = true;
-            }
-            // 7: Opacity (Slider, 0..100)
-            let op = op as f32 / 100.0;
-            if (self.network_opacity - op).abs() > 0.001 {
-                self.network_opacity = op;
-                changed = true;
-            }
-            // 8: Node Color (ColorSelector)
-            if let Some(col) = node_col {
-                let nr = col[0] as f32 / 255.0;
-                let ng = col[1] as f32 / 255.0;
-                let nb = col[2] as f32 / 255.0;
-                if (self.node_color[0] - nr).abs() > 0.001
-                    || (self.node_color[1] - ng).abs() > 0.001
-                    || (self.node_color[2] - nb).abs() > 0.001
-                {
-                    self.node_color = [nr, ng, nb];
-                    colors::set_node_color([nr, ng, nb, 1.0]);
-                    changed = true;
-                }
-            }
-            // 9: Cell Color (ColorSelector)
-            if let Some(col) = cell_col {
-                let cr = col[0] as f32 / 255.0;
-                let cg = col[1] as f32 / 255.0;
-                let cb = col[2] as f32 / 255.0;
-                if (self.cell_color[0] - cr).abs() > 0.001
-                    || (self.cell_color[1] - cg).abs() > 0.001
-                    || (self.cell_color[2] - cb).abs() > 0.001
-                {
-                    self.cell_color = [cr, cg, cb];
-                    changed = true;
-                }
-            }
-            // 10: Gap Color (ColorSelector)
-            if let Some(col) = gap_col {
-                let gr = col[0] as f32 / 255.0;
-                let gg = col[1] as f32 / 255.0;
-                let gb = col[2] as f32 / 255.0;
-                if (self.gap_color[0] - gr).abs() > 0.001
-                    || (self.gap_color[1] - gg).abs() > 0.001
-                    || (self.gap_color[2] - gb).abs() > 0.001
-                {
-                    self.gap_color = [gr, gg, gb];
-                    changed = true;
-                }
-            }
-        } else if let Some((sg, sc, so, cp, gt, os, cps, bgr, bgg, bgb, gcr, gcg, gcb)) = right_values {
-            // RIGHT pane Settings
-            // 0: Show Grid Guide (Checkbox)
-            let sg = sg == 1;
-            if self.show_grid != sg {
-                self.show_grid = sg;
-                self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 0, sg);
-                changed = true;
-            }
-            // 1: Show Reference Cube (Checkbox)
-            let sc = sc == 1;
-            if self.show_cube != sc {
-                self.show_cube = sc;
-                self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 1, sc);
-                changed = true;
-            }
-            // 2: Show Origin Axes (Checkbox)
-            let so = so == 1;
-            if self.show_origin != so {
-                self.show_origin = so;
-                self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 2, so);
-                changed = true;
-            }
-            // 3: Show Camera Pivot (Checkbox)
-            let cp = cp == 1;
-            if self.show_camera_pivot != cp {
-                self.show_camera_pivot = cp;
-                self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 3, cp);
-                changed = true;
-            }
-            // 4: Grid Thickness (Spinbox, decimals 3)
-            let gt = gt as f32 / 1000.0;
-            if (self.grid_thickness - gt).abs() > 0.0001 {
-                self.grid_thickness = gt;
-                self.update_grid_geometry();
-                changed = true;
-            }
-            // 5: Origin Guide Size (Spinbox, decimals 1)
-            let os = os as f32 / 10.0;
-            if (self.origin_size - os).abs() > 0.001 {
-                self.origin_size = os;
-                self.update_origin_geometry();
-                changed = true;
-            }
-            // 6: Camera Pivot Size (Spinbox, decimals 1)
-            let cps = cps as f32 / 10.0;
-            if (self.camera_pivot_size - cps).abs() > 0.001 {
-                self.camera_pivot_size = cps;
-                self.update_pivot_geometry();
-                changed = true;
-            }
-            // 7, 8, 9: BG Color (R, G, B Spinboxes, 0..255)
-            let bgr = bgr as f32 / 255.0;
-            let bgg = bgg as f32 / 255.0;
-            let bgb = bgb as f32 / 255.0;
-            if (self.viewport_bg_color[0] - bgr).abs() > 0.001
-                || (self.viewport_bg_color[1] - bgg).abs() > 0.001
-                || (self.viewport_bg_color[2] - bgb).abs() > 0.001
-            {
-                self.viewport_bg_color = [bgr, bgg, bgb];
-                changed = true;
-            }
-            // 10, 11, 12: Grid Color (R, G, B Spinboxes, 0..255)
-            let gcr = gcr as f32 / 255.0;
-            let gcg = gcg as f32 / 255.0;
-            let gcb = gcb as f32 / 255.0;
-            if (self.grid_color[0] - gcr).abs() > 0.001
-                || (self.grid_color[1] - gcg).abs() > 0.001
-                || (self.grid_color[2] - gcb).abs() > 0.001
-            {
-                self.grid_color = [gcr, gcg, gcb];
-                self.update_grid_geometry();
-                changed = true;
-            }
-        }
-
-        if changed {
-            self.sync_grid_settings();
-            self.save_settings();
-            self.upload_vertices();
-        }
-    }
 
     pub fn update_grid_geometry(&mut self) {
         let grid_verts = grid_vertices(self.grid_thickness, self.grid_color);
@@ -1375,30 +1172,348 @@ impl State {
         node
     }
 
+    pub fn get_lowest_unused_name(&self, base_name: &str) -> String {
+        let dir = self.current_dir();
+        let mut index = 1;
+        loop {
+            let candidate = format!("{} {}", base_name, index);
+            if !dir.children.iter().any(|c| c.name == candidate) {
+                return candidate;
+            }
+            index += 1;
+        }
+    }
+
     pub fn sync_parameters_to_project(&mut self) {
         if !self.is_detached_network {
             if let Some(slot_idx) = self.graph().selected_node() {
                 let updated_params = self.param().node_params();
+                let mut recent_file_to_open = None;
                 let dir = self.current_dir_mut();
                 if let Some(child) = dir.children.get_mut(slot_idx) {
                     let mut param_changed = false;
+                    let mut triggered_buttons = Vec::new();
                     for (u_name, u_val, _) in &updated_params {
                         if let Some(p) = child.params.iter_mut().find(|p| p.name == *u_name) {
                             if p.default != *u_val {
                                 p.default = u_val.clone();
                                 param_changed = true;
+                                if p.param_type == "button" && p.default == "clicked" {
+                                    triggered_buttons.push(p.name.clone());
+                                    p.default = "".to_string();
+                                }
+                                if p.name == "Open Recent" && p.default != "- Select -" && !p.default.is_empty() {
+                                    recent_file_to_open = Some(p.default.clone());
+                                    p.default = "- Select -".to_string();
+                                }
+                            }
+                        }
+                    }
+
+                    if !triggered_buttons.is_empty() || recent_file_to_open.is_some() {
+                        let mut disp_params = self.param().node_params();
+                        for btn_name in &triggered_buttons {
+                            if let Some(pos) = disp_params.iter().position(|p| p.0 == *btn_name) {
+                                disp_params[pos].1 = "".to_string();
+                            }
+                        }
+                        if recent_file_to_open.is_some() {
+                            if let Some(pos) = disp_params.iter().position(|p| p.0 == "Open Recent") {
+                                disp_params[pos].1 = "- Select -".to_string();
                             }
                         }
+                        self.param_mut().set_display_params(&disp_params);
                     }
+
                     if param_changed {
+                        self.apply_settings_from_menubar_subnets();
+                        self.sync_grid_settings();
                         self.rebuild_scene_geometry();
                         self.sync_nodes();
+
+                        for btn_name in triggered_buttons {
+                            match btn_name.as_str() {
+                                "Update Parameters" => {
+                                    if let Some(slot_idx) = self.graph().selected_node() {
+                                        let dir = self.current_dir_mut();
+                                        if let Some(child) = dir.children.get_mut(slot_idx) {
+                                            if child.node_type == "opencl" {
+                                                let code_val = child.params.iter()
+                                                    .find(|p| p.name == "Code")
+                                                    .map(|p| p.default.clone())
+                                                    .unwrap_or_default();
+                                                let parsed_params = crate::geometry::parse_dynamic_params(&code_val);
+                                                let mut new_params = Vec::new();
+                                                for base_name in &["Input", "Code", "Update Parameters"] {
+                                                    if let Some(p) = child.params.iter().find(|p| p.name == *base_name) {
+                                                        new_params.push(p.clone());
+                                                    }
+                                                }
+                                                for mut parsed in parsed_params {
+                                                    if let Some(existing) = child.params.iter().find(|p| p.name == parsed.name) {
+                                                        parsed.default = existing.default.clone();
+                                                    }
+                                                    new_params.push(parsed);
+                                                }
+                                                child.params = new_params;
+                                                let updated_disp = param_display(&child.params);
+                                                self.param_mut().set_display_params(&updated_disp);
+                                                self.rebuild_scene_geometry();
+                                                self.sync_nodes();
+                                            }
+                                        }
+                                    }
+                                }
+                                "New Project" | "New" => {
+                                    self.new_project();
+                                }
+                                "Open" => {
+                                    self.open_file_chooser();
+                                }
+                                "Save" => {
+                                    let path_opt = self.loaded_project_path.clone();
+                                    if let Some(path) = path_opt {
+                                        if let Err(e) = self.save_to_file(&path) {
+                                            eprintln!("Failed to save project: {:?}", e);
+                                            self.update_status_text(&format!("Failed to save: {:?}", e));
+                                        } else {
+                                            self.update_status_text(&format!("Project saved to {}", path.display()));
+                                            self.add_recent_file(path);
+                                        }
+                                    } else {
+                                        self.save_file_chooser();
+                                    }
+                                }
+                                "Save As" => {
+                                    self.save_file_chooser();
+                                }
+                                "Exit" => {
+                                    self.exit_requested = true;
+                                }
+                                "Zoom In" => {
+                                    self.zoom(1.15, None);
+                                }
+                                "Zoom Out" => {
+                                    self.zoom(1.0 / 1.15, None);
+                                }
+                                "Reset Zoom" => {
+                                    self.grid_size_x = 150.0;
+                                    self.grid_size_y = 75.0;
+                                    self.skipped_col_w = 37.5;
+                                    self.skipped_row_h = 37.5;
+                                    self.sync_grid_settings();
+                                }
+                                "Detach Circular Window" | "Detach Pane" => {
+                                    self.execute_action(Action::DetachCircularWindow);
+                                }
+                                "Show Network Pane" => {
+                                    self.show_network = !self.show_network;
+                                    self.widgets[CONTENT_IDX].set_visible(self.show_network);
+                                    self.widgets[LEFT_MENUBAR_IDX].set_visible(self.show_network);
+                                    self.widgets[BREADCRUMB_IDX].set_visible(self.show_network);
+                                    let val = self.show_network;
+                                    self.menu_mut(HEADER_IDX).set_item_checked(2, 4, val);
+                                    if !self.show_network && self.focused_pane == LEFT_MENUBAR_IDX {
+                                        self.focused_pane = get_next_visible_pane(
+                                            self.focused_pane,
+                                            self.show_network,
+                                            self.show_viewport,
+                                            self.show_parameters,
+                                            self.show_spreadsheet,
+                                            false,
+                                        );
+                                    }
+                                    self.rebuild_positions();
+                                    self.apply_layout();
+                                    self.sync_pane_focus();
+                                }
+                                "Show Viewport Pane" => {
+                                    self.show_viewport = !self.show_viewport;
+                                    self.widgets[VIEWPORT_IDX].set_visible(self.show_viewport);
+                                    self.widgets[RIGHT_MENUBAR_IDX].set_visible(self.show_viewport);
+                                    let val = self.show_viewport;
+                                    self.menu_mut(HEADER_IDX).set_item_checked(2, 5, val);
+                                    if !self.show_viewport && self.focused_pane == RIGHT_MENUBAR_IDX {
+                                        self.focused_pane = get_next_visible_pane(
+                                            self.focused_pane,
+                                            self.show_network,
+                                            self.show_viewport,
+                                            self.show_parameters,
+                                            self.show_spreadsheet,
+                                            false,
+                                        );
+                                    }
+                                    self.rebuild_positions();
+                                    self.apply_layout();
+                                    self.sync_pane_focus();
+                                }
+                                "Show Parameters Pane" => {
+                                    self.show_parameters = !self.show_parameters;
+                                    self.widgets[PARAM_IDX].set_visible(self.show_parameters);
+                                    self.widgets[PARAM_MENUBAR_IDX].set_visible(self.show_parameters);
+                                    let val = self.show_parameters;
+                                    self.menu_mut(HEADER_IDX).set_item_checked(2, 6, val);
+                                    if !self.show_parameters && self.focused_pane == PARAM_MENUBAR_IDX {
+                                        self.focused_pane = get_next_visible_pane(
+                                            self.focused_pane,
+                                            self.show_network,
+                                            self.show_viewport,
+                                            self.show_parameters,
+                                            self.show_spreadsheet,
+                                            false,
+                                        );
+                                    }
+                                    self.rebuild_positions();
+                                    self.apply_layout();
+                                    self.sync_pane_focus();
+                                }
+                                "Show Spreadsheet Pane" => {
+                                    self.show_spreadsheet = !self.show_spreadsheet;
+                                    self.widgets[SPREADSHEET_IDX].set_visible(self.show_spreadsheet);
+                                    self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(self.show_spreadsheet);
+                                    let val = self.show_spreadsheet;
+                                    self.menu_mut(HEADER_IDX).set_item_checked(2, 7, val);
+                                    if !self.show_spreadsheet && self.focused_pane == SPREADSHEET_MENUBAR_IDX {
+                                        self.focused_pane = get_next_visible_pane(
+                                            self.focused_pane,
+                                            self.show_network,
+                                            self.show_viewport,
+                                            self.show_parameters,
+                                            self.show_spreadsheet,
+                                            false,
+                                        );
+                                    }
+                                    self.rebuild_positions();
+                                    self.apply_layout();
+                                    self.sync_pane_focus();
+                                }
+                                "Close Pane" => {
+                                    let mut parent_name = "";
+                                    if self.current_path.len() >= 1 {
+                                        let root_idx = self.current_path[0];
+                                        if let Some(r_node) = self.fs_root.children.get(root_idx) {
+                                            parent_name = r_node.name.as_str();
+                                        }
+                                    }
+                                    match parent_name {
+                                        "Network" => {
+                                            self.show_network = false;
+                                            self.widgets[CONTENT_IDX].set_visible(false);
+                                            self.widgets[LEFT_MENUBAR_IDX].set_visible(false);
+                                            self.widgets[BREADCRUMB_IDX].set_visible(false);
+                                            self.menu_mut(HEADER_IDX).set_item_checked(2, 4, false);
+                                            if self.focused_pane == LEFT_MENUBAR_IDX {
+                                                self.focused_pane = get_next_visible_pane(
+                                                    self.focused_pane,
+                                                    self.show_network,
+                                                    self.show_viewport,
+                                                    self.show_parameters,
+                                                    self.show_spreadsheet,
+                                                    false,
+                                                );
+                                            }
+                                            self.rebuild_positions();
+                                            self.apply_layout();
+                                            self.sync_pane_focus();
+                                        }
+                                        "Viewport" => {
+                                            self.show_viewport = false;
+                                            self.widgets[VIEWPORT_IDX].set_visible(false);
+                                            self.widgets[RIGHT_MENUBAR_IDX].set_visible(false);
+                                            self.menu_mut(HEADER_IDX).set_item_checked(2, 5, false);
+                                            if self.focused_pane == RIGHT_MENUBAR_IDX {
+                                                self.focused_pane = get_next_visible_pane(
+                                                    self.focused_pane,
+                                                    self.show_network,
+                                                    self.show_viewport,
+                                                    self.show_parameters,
+                                                    self.show_spreadsheet,
+                                                    false,
+                                                );
+                                            }
+                                            self.rebuild_positions();
+                                            self.apply_layout();
+                                            self.sync_pane_focus();
+                                        }
+                                        "Parameters" => {
+                                            self.show_parameters = false;
+                                            self.widgets[PARAM_IDX].set_visible(false);
+                                            self.widgets[PARAM_MENUBAR_IDX].set_visible(false);
+                                            self.menu_mut(HEADER_IDX).set_item_checked(2, 6, false);
+                                            if self.focused_pane == PARAM_MENUBAR_IDX {
+                                                self.focused_pane = get_next_visible_pane(
+                                                    self.focused_pane,
+                                                    self.show_network,
+                                                    self.show_viewport,
+                                                    self.show_parameters,
+                                                    self.show_spreadsheet,
+                                                    false,
+                                                );
+                                            }
+                                            self.rebuild_positions();
+                                            self.apply_layout();
+                                            self.sync_pane_focus();
+                                        }
+                                        "Spreadsheet" => {
+                                            self.show_spreadsheet = false;
+                                            self.widgets[SPREADSHEET_IDX].set_visible(false);
+                                            self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(false);
+                                            self.menu_mut(HEADER_IDX).set_item_checked(2, 7, false);
+                                            if self.focused_pane == SPREADSHEET_MENUBAR_IDX {
+                                                self.focused_pane = get_next_visible_pane(
+                                                    self.focused_pane,
+                                                    self.show_network,
+                                                    self.show_viewport,
+                                                    self.show_parameters,
+                                                    self.show_spreadsheet,
+                                                    false,
+                                                );
+                                            }
+                                            self.rebuild_positions();
+                                            self.apply_layout();
+                                            self.sync_pane_focus();
+                                        }
+                                        _ => {}
+                                    }
+                                }
+                                _ => {}
+                            }
+                        }
+                    }
+                }
+
+                if let Some(path_str) = recent_file_to_open {
+                    let path = std::path::PathBuf::from(path_str);
+                    if let Err(e) = self.load_from_file(&path) {
+                        eprintln!("Failed to load recent file: {:?}", e);
+                        self.update_status_text(&format!("Failed to load: {:?}", e));
+                    } else {
+                        self.update_status_text(&format!("Loaded project from {}", path.display()));
+                        self.add_recent_file(path);
                     }
                 }
             }
         }
     }
 
+    pub fn sync_parameters_pane(&mut self) {
+        let params = if !self.is_detached_network {
+            if let Some(slot_idx) = self.graph().selected_node() {
+                let dir = self.current_dir();
+                if slot_idx < dir.children.len() {
+                    param_display(&dir.children[slot_idx].params)
+                } else {
+                    vec![]
+                }
+            } else {
+                vec![]
+            }
+        } else {
+            vec![]
+        };
+        self.param_mut().set_display_params(&params);
+    }
+
     pub fn current_path_names(&self) -> Vec<String> {
         let mut node = &self.fs_root;
         let mut names = Vec::new();
@@ -1442,14 +1557,34 @@ impl State {
 
             // Find executable path
             let exe_path = if let Ok(cur_exe) = std::env::current_exe() {
-                let sibling = cur_exe.with_file_name("clear-filesystem-interface");
+                let sibling = cur_exe.with_file_name("cce-filesystem-interface");
                 if sibling.exists() {
                     sibling
+                } else if let Ok(home) = std::env::var("HOME") {
+                    let path = std::path::PathBuf::from(home)
+                        .join(".local")
+                        .join("bin")
+                        .join("cce-filesystem-interface");
+                    if path.exists() {
+                        path
+                    } else {
+                        std::path::PathBuf::from("cce-filesystem-interface")
+                    }
+                } else {
+                    std::path::PathBuf::from("cce-filesystem-interface")
+                }
+            } else if let Ok(home) = std::env::var("HOME") {
+                let path = std::path::PathBuf::from(home)
+                    .join(".local")
+                    .join("bin")
+                    .join("cce-filesystem-interface");
+                if path.exists() {
+                    path
                 } else {
-                    std::path::PathBuf::from("clear-filesystem-interface")
+                    std::path::PathBuf::from("cce-filesystem-interface")
                 }
             } else {
-                std::path::PathBuf::from("clear-filesystem-interface")
+                std::path::PathBuf::from("cce-filesystem-interface")
             };
 
             let child = match Command::new(exe_path)
@@ -1459,7 +1594,7 @@ impl State {
             {
                 Ok(c) => c,
                 Err(e) => {
-                    eprintln!("Failed to spawn clear-filesystem-interface: {:?}", e);
+                    eprintln!("Failed to spawn cce-filesystem-interface: {:?}", e);
                     return;
                 }
             };
@@ -1467,7 +1602,7 @@ impl State {
             let output = match child.wait_with_output() {
                 Ok(o) => o,
                 Err(e) => {
-                    eprintln!("Failed to wait for clear-filesystem-interface: {:?}", e);
+                    eprintln!("Failed to wait for cce-filesystem-interface: {:?}", e);
                     return;
                 }
             };
@@ -1505,14 +1640,34 @@ impl State {
 
             // Find executable path
             let exe_path = if let Ok(cur_exe) = std::env::current_exe() {
-                let sibling = cur_exe.with_file_name("clear-filesystem-interface");
+                let sibling = cur_exe.with_file_name("cce-filesystem-interface");
                 if sibling.exists() {
                     sibling
+                } else if let Ok(home) = std::env::var("HOME") {
+                    let path = std::path::PathBuf::from(home)
+                        .join(".local")
+                        .join("bin")
+                        .join("cce-filesystem-interface");
+                    if path.exists() {
+                        path
+                    } else {
+                        std::path::PathBuf::from("cce-filesystem-interface")
+                    }
                 } else {
-                    std::path::PathBuf::from("clear-filesystem-interface")
+                    std::path::PathBuf::from("cce-filesystem-interface")
+                }
+            } else if let Ok(home) = std::env::var("HOME") {
+                let path = std::path::PathBuf::from(home)
+                    .join(".local")
+                    .join("bin")
+                    .join("cce-filesystem-interface");
+                if path.exists() {
+                    path
+                } else {
+                    std::path::PathBuf::from("cce-filesystem-interface")
                 }
             } else {
-                std::path::PathBuf::from("clear-filesystem-interface")
+                std::path::PathBuf::from("cce-filesystem-interface")
             };
 
             let child = match Command::new(exe_path)
@@ -1522,7 +1677,7 @@ impl State {
             {
                 Ok(c) => c,
                 Err(e) => {
-                    eprintln!("Failed to spawn clear-filesystem-interface: {:?}", e);
+                    eprintln!("Failed to spawn cce-filesystem-interface: {:?}", e);
                     return;
                 }
             };
@@ -1530,7 +1685,7 @@ impl State {
             let output = match child.wait_with_output() {
                 Ok(o) => o,
                 Err(e) => {
-                    eprintln!("Failed to wait for clear-filesystem-interface: {:?}", e);
+                    eprintln!("Failed to wait for cce-filesystem-interface: {:?}", e);
                     return;
                 }
             };
@@ -1579,14 +1734,24 @@ impl State {
     pub fn place_selected_node(&mut self) -> bool {
         let Some(&template_idx) = self.node_palette_filtered.get(self.node_palette_selected) else { return false; };
         let mut node = self.node_templates[template_idx].node.clone();
+        let is_in_utility = !self.current_path.is_empty() && self.fs_root.children[self.current_path[0]].node_type == "utility";
+        if is_in_utility {
+            if crate::geometry::is_geometry_node_type(&node.node_type) {
+                self.update_status_text("Utility nodes cannot contain geometry.");
+                self.close_node_palette();
+                return false;
+            }
+        }
         let (nx, ny) = self.find_empty_cell(self.grid_cursor_col as f32, self.grid_cursor_row as f32, None);
         node.position = (nx, ny);
+        node.name = self.get_lowest_unused_name(&node.name);
         self.current_dir_mut().children.push(node);
         self.close_node_palette();
         self.sync_nodes();
         self.rebuild_positions();
         self.apply_layout();
         self.update_panel_bounds();
+        self.rebuild_scene_geometry();
         self.upload_vertices();
         true
     }
@@ -1641,7 +1806,7 @@ impl State {
         }
     }
 
-fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String>>) {
+pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String>>) {
     let mut headers = vec![
         "Vertex".to_string(),
         "Pos.x".to_string(),
@@ -1837,6 +2002,33 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.apply_layout();
         self.update_panel_bounds();
         self.upload_vertices();
+        self.sync_cursor_and_selection();
+    }
+
+    pub fn move_up(&mut self) -> bool {
+        if !self.current_path.is_empty() {
+            let exited_idx = self.current_path.pop();
+            self.on_path_changed();
+            if let Some(idx) = exited_idx {
+                let pos = {
+                    let dir = self.current_dir();
+                    if idx < dir.children.len() {
+                        Some(dir.children[idx].position)
+                    } else {
+                        None
+                    }
+                };
+                if let Some((pos_x, pos_y)) = pos {
+                    self.grid_cursor_col = pos_x as i32;
+                    self.grid_cursor_row = pos_y as i32;
+                    self.sync_cursor_and_selection();
+                    self.upload_vertices();
+                }
+            }
+            true
+        } else {
+            false
+        }
     }
 
     pub fn find_empty_cell(&self, start_x: f32, start_y: f32, skip_idx: Option<usize>) -> (f32, f32) {
@@ -1860,6 +2052,30 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         (x, y)
     }
 
+    pub fn delete_node(&mut self, slot: usize) -> bool {
+        let len = self.current_dir().children.len();
+        if slot < len {
+            self.current_dir_mut().children.remove(slot);
+            if let Some(sel_idx) = self.graph().selected_node() {
+                if sel_idx == slot {
+                    self.graph_mut().set_selected_node(None);
+                } else if sel_idx > slot {
+                    self.graph_mut().set_selected_node(Some(sel_idx - 1));
+                }
+            }
+            self.sync_nodes();
+            self.rebuild_positions();
+            self.apply_layout();
+            self.update_panel_bounds();
+            self.rebuild_scene_geometry();
+            self.upload_vertices();
+            self.viewport_dirty = true;
+            true
+        } else {
+            false
+        }
+    }
+
     pub fn sync_nodes(&mut self) {
         let graph_nodes: Vec<GraphNode> = self.current_dir().children.iter().map(|c| {
             GraphNode {
@@ -1868,6 +2084,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 position: c.position,
                 parameters: param_display(&c.params),
                 geom_visible: c.geometry_visible,
+                node_type: c.node_type.clone(),
+                inputs: c.inputs,
+                outputs: c.outputs,
             }
         }).collect();
         self.graph_mut().set_nodes(&graph_nodes);
@@ -1916,57 +2135,17 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             }
         }
 
-        if !cache_hit {
+        if self.show_spreadsheet && !cache_hit {
             let mut headers = Vec::new();
             let mut rows = Vec::new();
 
             if let Some(node) = selected_node {
-                if node.node_type.eq_ignore_ascii_case("sphere") {
-                    if let Some(idx) = find_sphere_index(&self.fs_root, node) {
-                        let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
-                        let radius = node_param_f32(node, "Radius", 0.5).max(0.05);
-                        let geom = sphere_vertices(center, radius);
-                        let (h, r) = Self::geometry_to_spreadsheet_data(&geom);
-                        headers = h;
-                        rows = r;
-                    }
-                } else if node.node_type.eq_ignore_ascii_case("line") {
-                    if let Some(idx) = find_sphere_index(&self.fs_root, node) {
-                        let start = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
-                        let length = node_param_f32(node, "Length", 1.0);
-                        let thickness = node_param_f32(node, "Thickness", 0.02);
-                        let end = start + Vec3::new(0.0, length, 0.0);
-                        let geom = line_vertices(start, end, thickness);
-                        let (h, r) = Self::geometry_to_spreadsheet_data(&geom);
-                        headers = h;
-                        rows = r;
-                    }
-                } else if node.node_type.eq_ignore_ascii_case("add") {
-                    if let Some(idx) = find_sphere_index(&self.fs_root, node) {
-                        let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
-                        let num_points = node_param_f32(node, "Points", 100.0) as i32;
-                        let mut geom = Geometry::new();
-                        for i in 0..num_points {
-                            let t = i as f32 / num_points.max(1) as f32;
-                            let angle = t * std::f32::consts::TAU * 3.0;
-                            let r = 0.4 * t;
-                            let px = center.x + r * angle.cos();
-                            let py = center.y + t * 0.5 - 0.25;
-                            let pz = center.z + r * angle.sin();
-                            let pt_center = Vec3::new(px, py, pz);
-                            geom.merge(sphere_vertices(pt_center, 0.02));
-                        }
-                        let (h, r) = Self::geometry_to_spreadsheet_data(&geom);
-                        headers = h;
-                        rows = r;
-                    }
-                } else if node.node_type.eq_ignore_ascii_case("transform") {
-                    let mut visited = Vec::new();
-                    if let Some(geom) = resolve_transform_geometry(&self.fs_root, node, &mut visited) {
-                        let (h, r) = Self::geometry_to_spreadsheet_data(&geom);
-                        headers = h;
-                        rows = r;
-                    }
+                let mut visited = Vec::new();
+                let mut ocl_error = None;
+                if let Some(geom) = generate_single_node_geometry_with_errors(&self.fs_root, node, &mut visited, &mut ocl_error) {
+                    let (h, r) = Self::geometry_to_spreadsheet_data(&geom);
+                    headers = h;
+                    rows = r;
                 }
             }
 
@@ -2457,6 +2636,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 params: vec![],
                 geometry_visible: true,
                 position: (0.0, 0.0),
+                inputs: 0,
+                outputs: 0,
             }
         };
 
@@ -2493,8 +2674,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             Box::new(Plate::new(0.0, 0.0, 0.0, 0.0).with_color(colors::PARAM_BG).with_blur(true)),
             Box::new(ParametersBg::new()),
             Box::new(Canvas::new()),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Open", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_item("Settings", &[]).with_context_options(context_opts.clone(), 0)),
-            Box::new(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", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_item("Settings", &[]).with_context_options(context_opts.clone(), 1)),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Open", "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)),
+            Box::new(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", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 1)),
             Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default", "Custom"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2)),
             Box::new(StatusBar::new().with_text("Ready")),
             Box::new(Breadcrumb::new()),
@@ -2509,11 +2690,12 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         let network_panel = Plate::new(0.0, 0.0, 0.0, 0.0).with_color([0.10, 0.10, 0.13, 0.95]);
         widgets.push(Box::new(network_panel));
 
-        let paginator = Paginator::new(56.0, vec![])
+        let mut paginator = Paginator::new(56.0, vec![])
             .with_sidebar_mode(true)
             .with_column_layout(true)
             .with_tabs_rotated(false)
             .with_context_options(context_opts.clone(), 0);
+        paginator.set_page_hidden(true);
         widgets.push(Box::new(paginator));
 
         let mut positions = Vec::with_capacity(PAGINATOR_IDX + 1);
@@ -2602,6 +2784,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             fs_root: fs_root.clone(),
             node_templates,
             current_path,
+            node_clipboard: None,
             last_click: None,
             last_frame: Instant::now(),
             shortcut_manager,
@@ -2638,8 +2821,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             physical_height: ph,
             scale,
             square_viewport: settings.square_viewport,
-            grid_snap_enabled: settings.grid_snap_enabled,
-            network_grid_visible: settings.network_grid_enabled,
+            grid_snap_enabled: true,
+            network_grid_visible: true,
             grid_size_x: settings.grid_size_x,
             grid_size_y: settings.grid_size_y,
             skipped_row_h: settings.skipped_row_h,
@@ -2696,8 +2879,10 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             window_y: 0,
             active_menu_cloud_pid: None,
             active_menu_cloud_idx: None,
-            uniform_background: settings.uniform_background,
-            network_opacity: settings.network_opacity,
+            uniform_background: false,
+            network_opacity: 0.95,
+            cell_opacity: 0.95,
+            gap_opacity: 0.95,
             last_design_mod_time: {
                 let design_path = DesignSettings::file_path();
                 std::fs::metadata(&design_path).and_then(|m| m.modified()).ok()
@@ -2737,6 +2922,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             recent_files_buttons,
             text_buffer_cache: std::collections::HashMap::new(),
             viewport_dirty: true,
+            text_dirty: true,
+            last_popover_rects: Vec::new(),
+            last_status_text: String::new(),
             last_viewport_camera_pos: Vec3::ZERO,
             last_viewport_camera_rx: 0.0,
             last_viewport_camera_ry: 0.0,
@@ -2758,6 +2946,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         };
 
         state.update_inertial_settings();
+        state.update_graph_settings_from_config();
         state.update_window_title();
         colors::set_node_color([
             settings.node_color[0],
@@ -2798,6 +2987,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         state.update_panel_bounds();
         state.sync_pane_focus();
         state.upload_vertices();
+        state.sync_cursor_and_selection();
+        state.sync_parameters_pane();
         state
     }
 
@@ -2821,7 +3012,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         graph.set_grid_snap_enabled(grid_snap_enabled);
         if let Some(graph) = self.widgets[CONTENT_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Graph>() {
             graph.set_uniform_background(self.uniform_background);
-            graph.set_network_opacity(self.network_opacity);
+            graph.set_cell_opacity(self.cell_opacity);
+            graph.set_gap_opacity(self.gap_opacity);
             graph.set_cell_color(self.cell_color);
             graph.set_gap_color(self.gap_color);
         }
@@ -2875,6 +3067,94 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.scroll_speed = speed;
     }
 
+    pub fn update_graph_settings_from_config(&mut self) {
+        let config_paths = [
+            "/home/lsgalante/.config/cce/config.toml",
+            "/home/lsgalante/.config/ccec/config.toml",
+        ];
+        
+        let mut show_grid = None;
+        let mut snap_enabled = None;
+        let mut uniform_background = None;
+        let mut cell_opacity = None;
+        let mut gap_opacity = None;
+        let mut network_opacity = None;
+
+        for path in &config_paths {
+            if let Ok(content) = std::fs::read_to_string(path) {
+                #[derive(serde::Deserialize)]
+                struct Layout {
+                    pub graph_show_grid: Option<bool>,
+                    pub graph_snap_enabled: Option<bool>,
+                    pub graph_uniform_background: Option<bool>,
+                    pub graph_network_opacity: Option<f32>,
+                    pub graph_cell_opacity: Option<f32>,
+                    pub graph_gap_opacity: Option<f32>,
+                }
+                #[derive(serde::Deserialize)]
+                struct Config {
+                    pub layout: Option<Layout>,
+                }
+                if let Ok(cfg) = toml::from_str::<Config>(&content) {
+                    if let Some(layout) = cfg.layout {
+                        show_grid = layout.graph_show_grid;
+                        snap_enabled = layout.graph_snap_enabled;
+                        uniform_background = layout.graph_uniform_background;
+                        network_opacity = layout.graph_network_opacity;
+                        cell_opacity = layout.graph_cell_opacity.or(layout.graph_network_opacity);
+                        gap_opacity = layout.graph_gap_opacity.or(layout.graph_network_opacity);
+                        break;
+                    }
+                }
+            }
+        }
+
+        let mut changed = false;
+        if let Some(val) = show_grid {
+            if self.show_grid != val {
+                self.show_grid = val;
+                self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 0, val);
+                changed = true;
+            }
+        }
+        if let Some(val) = snap_enabled {
+            if self.grid_snap_enabled != val {
+                self.grid_snap_enabled = val;
+                changed = true;
+            }
+        }
+        if let Some(val) = uniform_background {
+            if self.uniform_background != val {
+                self.uniform_background = val;
+                changed = true;
+            }
+        }
+        if let Some(val) = network_opacity {
+            if (self.network_opacity - val).abs() > 0.001 {
+                self.network_opacity = val;
+                changed = true;
+            }
+        }
+        if let Some(val) = cell_opacity {
+            if (self.cell_opacity - val).abs() > 0.001 {
+                self.cell_opacity = val;
+                changed = true;
+            }
+        }
+        if let Some(val) = gap_opacity {
+            if (self.gap_opacity - val).abs() > 0.001 {
+                self.gap_opacity = val;
+                changed = true;
+            }
+        }
+        
+        if changed {
+            self.sync_grid_settings();
+            self.viewport_dirty = true;
+        }
+    }
+
+
 
     pub fn zoom(&mut self, factor: f32, center: Option<(f32, f32)>) {
         self.pan_velocity_x = 0.0;
@@ -3403,163 +3683,28 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.paginator_page_widgets.resize_with(num_pages, Vec::new);
 
             for (page_idx, page_name) in menu_names.iter().enumerate() {
-                if page_name == "Settings" {
-                    if active_menubar == LEFT_MENUBAR_IDX {
-                        // 0: Snap to Grid (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Snap to Grid");
-                        cb.set_checked(self.grid_snap_enabled);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 1: Grid Visible (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Grid Visible");
-                        cb.set_checked(self.network_grid_visible);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 2: Grid X (Spinbox, range 10-200)
-                        let mut sb = Spinbox::new(self.grid_size_x as i32, 10, 200, 1).with_label("Grid X");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 3: Grid Y (Spinbox, range 5-100)
-                        let mut sb = Spinbox::new(self.grid_size_y as i32, 5, 100, 1).with_label("Grid Y");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 4: Skipped Row H (Spinbox, range 0-150)
-                        let mut sb = Spinbox::new(self.skipped_row_h as i32, 0, 150, 1).with_label("Skipped Row H");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 5: Skipped Col W (Spinbox, range 0-150)
-                        let mut sb = Spinbox::new(self.skipped_col_w as i32, 0, 150, 1).with_label("Skipped Col W");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 6: Uniform Background (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Uniform Background");
-                        cb.set_checked(self.uniform_background);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 7: Opacity (Slider, range 0.0-1.0)
-                        let mut sl = Slider::new().with_range(0.0, 1.0).with_value(self.network_opacity).with_label("Opacity").with_readout(true).with_scroll(true);
-                        sl.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sl));
-
-                        // 8: Node Color (ColorSelector)
-                        let mut cs = ColorSelector::new([
-                            (self.node_color[0] * 255.0) as u8,
-                            (self.node_color[1] * 255.0) as u8,
-                            (self.node_color[2] * 255.0) as u8,
-                        ]).with_label("Node Color");
-                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
-
-                        // 9: Cell Color (ColorSelector)
-                        let mut cs = ColorSelector::new([
-                            (self.cell_color[0] * 255.0) as u8,
-                            (self.cell_color[1] * 255.0) as u8,
-                            (self.cell_color[2] * 255.0) as u8,
-                        ]).with_label("Cell Color");
-                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
-
-                        // 10: Gap Color (ColorSelector)
-                        let mut cs = ColorSelector::new([
-                            (self.gap_color[0] * 255.0) as u8,
-                            (self.gap_color[1] * 255.0) as u8,
-                            (self.gap_color[2] * 255.0) as u8,
-                        ]).with_label("Gap Color");
-                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
-                    } else if active_menubar == RIGHT_MENUBAR_IDX {
-                        // 0: Show Grid Guide (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Show Grid Guide");
-                        cb.set_checked(self.show_grid);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 1: Show Reference Cube (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Show Reference Cube");
-                        cb.set_checked(self.show_cube);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 2: Show Origin Axes (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Show Origin Axes");
-                        cb.set_checked(self.show_origin);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 3: Show Camera Pivot (Checkbox)
-                        let mut cb = Checkbox::new().with_label("Show Camera Pivot");
-                        cb.set_checked(self.show_camera_pivot);
-                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                let items = &menu_items[page_idx];
+                let checked_list = menu_checked.get(page_idx);
+                for (item_idx, item_name) in items.iter().enumerate() {
+                    let is_checked = checked_list.and_then(|l| l.get(item_idx).copied().flatten());
+                    if let Some(checked_val) = is_checked {
+                        let mut cb = Checkbox::new().with_label(item_name);
+                        cb.set_checked(checked_val);
                         self.paginator_page_widgets[page_idx].push(Box::new(cb));
-
-                        // 4: Grid Thickness (Spinbox, range 2-200, decimals 3)
-                        let mut sb = Spinbox::new((self.grid_thickness * 1000.0) as i32, 2, 200, 1).with_label("Grid Thickness").with_decimals(3);
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 5: Origin Guide Size (Spinbox, range 1-50, decimals 1)
-                        let mut sb = Spinbox::new((self.origin_size * 10.0) as i32, 1, 50, 1).with_label("Origin Guide Size").with_decimals(1);
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 6: Camera Pivot Size (Spinbox, range 1-50, decimals 1)
-                        let mut sb = Spinbox::new((self.camera_pivot_size * 10.0) as i32, 1, 50, 1).with_label("Camera Pivot Size").with_decimals(1);
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 7, 8, 9: BG Color R, G, B (Spinbox, range 0-255)
-                        let mut sb = Spinbox::new((self.viewport_bg_color[0] * 255.0) as i32, 0, 255, 1).with_label("BG Color R");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-                        let mut sb = Spinbox::new((self.viewport_bg_color[1] * 255.0) as i32, 0, 255, 1).with_label("BG Color G");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-                        let mut sb = Spinbox::new((self.viewport_bg_color[2] * 255.0) as i32, 0, 255, 1).with_label("BG Color B");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-
-                        // 10, 11, 12: Grid Color R, G, B (Spinbox, range 0-255)
-                        let mut sb = Spinbox::new((self.grid_color[0] * 255.0) as i32, 0, 255, 1).with_label("Grid Color R");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-                        let mut sb = Spinbox::new((self.grid_color[1] * 255.0) as i32, 0, 255, 1).with_label("Grid Color G");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-                        let mut sb = Spinbox::new((self.grid_color[2] * 255.0) as i32, 0, 255, 1).with_label("Grid Color B");
-                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
-                    }
-                } else {
-                    let items = &menu_items[page_idx];
-                    let checked_list = menu_checked.get(page_idx);
-                    for (item_idx, item_name) in items.iter().enumerate() {
-                        let is_checked = checked_list.and_then(|l| l.get(item_idx).copied().flatten());
-                        if let Some(checked_val) = is_checked {
-                            let mut cb = Checkbox::new().with_label(item_name);
-                            cb.set_checked(checked_val);
-                            self.paginator_page_widgets[page_idx].push(Box::new(cb));
-                        } else {
-                            let btn = Button::new(0.0, 0.0, 150.0, 24.0).with_label(item_name);
-                            self.paginator_page_widgets[page_idx].push(Box::new(btn));
-                        }
+                    } else {
+                        let btn = Button::new(0.0, 0.0, 150.0, 24.0).with_label(item_name);
+                        self.paginator_page_widgets[page_idx].push(Box::new(btn));
                     }
+                }
 
-                    if menu_names[page_idx] == "File" {
-                        let mut recent_lbl = Label::new("Recent Files").with_font_size(11.0).with_color([0xd4, 0xd4, 0xd4]);
-                        recent_lbl.set_rect(0.0, 0.0, 150.0, 16.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(recent_lbl));
+                if page_name == "File" {
+                    let mut recent_lbl = Label::new("Recent Files").with_font_size(11.0).with_color([0xd4, 0xd4, 0xd4]);
+                    recent_lbl.set_rect(0.0, 0.0, 150.0, 16.0);
+                    self.paginator_page_widgets[page_idx].push(Box::new(recent_lbl));
 
-                        let mut recent_list = ScrollingList::new(22.0, 2.0);
-                        recent_list.set_rect(0.0, 0.0, 150.0, 100.0);
-                        self.paginator_page_widgets[page_idx].push(Box::new(recent_list));
-                    }
+                    let mut recent_list = ScrollingList::new(22.0, 2.0);
+                    recent_list.set_rect(0.0, 0.0, 150.0, 100.0);
+                    self.paginator_page_widgets[page_idx].push(Box::new(recent_list));
                 }
             }
 
@@ -3772,7 +3917,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
     }
 
     pub fn sync_cursor_and_selection(&mut self) {
-        if self.focused_widget != Some(CONTENT_IDX) {
+        if self.focused_pane != LEFT_MENUBAR_IDX {
             return;
         }
         let dir = self.current_dir();
@@ -3797,6 +3942,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         }
     }
 
+    pub fn sync_cursor_and_selection_from_loaded(&mut self) {
+        if let Some(sel_idx) = self.graph().selected_node() {
+            let pos = {
+                let dir = self.current_dir();
+                if sel_idx < dir.children.len() {
+                    Some(dir.children[sel_idx].position)
+                } else {
+                    None
+                }
+            };
+            if let Some((pos_x, pos_y)) = pos {
+                self.grid_cursor_col = pos_x as i32;
+                self.grid_cursor_row = pos_y as i32;
+            }
+        } else {
+            self.sync_cursor_and_selection();
+        }
+    }
+
 
 
 
@@ -3943,21 +4107,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     self.sync_parameters_to_project();
 
                     // Sync Parameters pane with selected node
-                    let params = if !self.is_detached_network {
-                        if let Some(slot_idx) = self.graph().selected_node() {
-                            let dir = self.current_dir();
-                            if slot_idx < dir.children.len() {
-                                param_display(&dir.children[slot_idx].params)
-                            } else {
-                                vec![]
-                            }
-                        } else {
-                            vec![]
-                        }
-                    } else {
-                        vec![]
-                    };
-                    self.param_mut().set_display_params(&params);
+                    self.sync_parameters_pane();
 
 
                     self.sync_layout();
@@ -4217,6 +4367,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                 if idx == NETWORK_PANEL_IDX {
                                     self.sync_grid_settings();
                                 }
+                                if idx == PARAM_IDX {
+                                    self.sync_parameters_to_project();
+                                }
                             }
                         }
                     }
@@ -4269,6 +4422,16 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     self.is_zooming_viewport = false;
                     self.zoom_accum = 0.0;
                 }
+                if self.node_palette_visible {
+                    if *btn_state == ElementState::Pressed {
+                        let (px, py, pw, ph) = self.palette().panel_rect();
+                        if self.cursor_x < px || self.cursor_x > px + pw || self.cursor_y < py || self.cursor_y > py + ph {
+                            self.close_node_palette();
+                        }
+                    }
+                    self.upload_vertices();
+                    return true;
+                }
                 let dialog_open = self.node_palette_visible;
                 let in_network_pane = self.in_network_pane();
                 let node_area_x = self.positions[CONTENT_IDX].0;
@@ -4344,7 +4507,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     ElementState::Pressed => {
                         let hits_any_menu = (0..self.widgets.len()).any(|i| {
                             hits_widget(self, i, self.cursor_x, self.cursor_y)
-                                && self.menu(i).get_menu_items_at(self.cursor_x, self.cursor_y).is_some()
+                                && self.widgets[i].as_menu_controller().and_then(|m| m.get_menu_items_at(self.cursor_x, self.cursor_y)).is_some()
                         });
 
                         if !hits_any_menu {
@@ -4512,7 +4675,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                         }
                         let mut click_target = None;
                         for i in 0..self.widgets.len() {
-                            if self.menu(i).is_menu_open() && hits_widget(self, i, self.cursor_x, self.cursor_y) {
+                            if self.widgets[i].as_menu_controller().map(|m| m.is_menu_open()).unwrap_or(false)
+                                && hits_widget(self, i, self.cursor_x, self.cursor_y)
+                            {
                                 click_target = Some(i);
                                 break;
                             }
@@ -4630,12 +4795,15 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             if i != PARAM_IDX {
                                 self.widgets[i].focus();
                                 self.focused_widget = Some(i);
-                                if self.menu(i).is_menu_bar() && !self.widgets[i].focused(&self.ui_context) {
+                                if self.widgets[i].as_menu_controller().map(|m| m.is_menu_bar()).unwrap_or(false)
+                                    && !self.widgets[i].focused(&self.ui_context)
+                                {
                                     self.widgets[i].unfocus();
                                     self.focused_widget = None;
                                 }
                             }
                             if i == CONTENT_IDX {
+                                self.sync_parameters_pane();
                                 if let Some(slot_idx) = self.graph().selected_node() {
                                     let dir = self.current_dir();
                                     if slot_idx < dir.children.len() {
@@ -4653,7 +4821,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                 if let Some(dir_idx) = self.graph().double_clicked_node() {
                                     self.graph_mut().clear_double_clicked_node();
                                     let dir = self.current_dir();
-                                    if dir_idx < dir.children.len() && (dir.children[dir_idx].node_type == "node" || dir.children[dir_idx].node_type == "opencl" || !dir.children[dir_idx].children.is_empty()) {
+                                    if dir_idx < dir.children.len() && (dir.children[dir_idx].node_type == "node" || dir.children[dir_idx].node_type == "utility" || !dir.children[dir_idx].children.is_empty()) {
                                         self.current_path.push(dir_idx);
                                         self.on_path_changed();
                                         changed = true;
@@ -4707,13 +4875,31 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             self.drag_widget = None;
                             changed = true;
                         }
-                        let ctx = &mut self.ui_context;
-                        for w in &mut self.widgets {
-                            w.set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
-                            if w.mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y, ctx) {
-                                changed = true;
+                        let mut sync_params = false;
+                        let mut sync_paginator = false;
+                        {
+                            let ctx = &mut self.ui_context;
+                            for (i, w) in self.widgets.iter_mut().enumerate() {
+                                w.set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+                                if w.mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y, ctx) {
+                                    changed = true;
+                                    if i == PARAM_IDX {
+                                        sync_params = true;
+                                    }
+                                    if i == PAGINATOR_IDX {
+                                        sync_paginator = true;
+                                    }
+                                }
                             }
                         }
+                        if sync_params {
+                            self.sync_parameters_to_project();
+                        }
+                        if sync_paginator {
+                            self.update_paginator();
+                            self.rebuild_positions();
+                            self.apply_layout();
+                        }
                     }
                 }
 
@@ -4724,6 +4910,20 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     changed = true;
                 }
 
+                if let Some((input_node_id, output_node_name)) = self.graph_mut().take_pending_connection() {
+                    let dir = self.current_dir_mut();
+                    if let Some(child) = dir.children.iter_mut().find(|c| c.id == input_node_id) {
+                        if let Some(p) = child.params.iter_mut().find(|p| p.name == "Input") {
+                            p.default = output_node_name;
+                            self.sync_nodes();
+                            self.rebuild_scene_geometry();
+                            self.upload_vertices();
+                            self.sync_parameters_pane();
+                            changed = true;
+                        }
+                    }
+                }
+
                 if self.page_selector(PAGINATOR_IDX).is_page_hidden() != was_page_hidden {
                     self.rebuild_positions();
                     self.apply_layout();
@@ -4769,10 +4969,17 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 if self.node_palette_visible {
                     return self.handle_node_palette_key(event);
                 }
+
+                if event.state == ElementState::Pressed && event.logical_key == Key::Named(NamedKey::Escape) {
+                    self.graph_mut().cancel_connecting();
+                    self.upload_vertices();
+                    return true;
+                }
                 let mut changed = false;
                 if event.state == ElementState::Pressed {
                         let is_plain_key = !self.modifiers.control_key() && !self.modifiers.alt_key() && !self.modifiers.super_key();
                         let is_alt_key = self.modifiers.alt_key() && !self.modifiers.control_key() && !self.modifiers.super_key();
+                        let is_ctrl_only = self.modifiers.control_key() && !self.modifiers.alt_key() && !self.modifiers.super_key() && !self.modifiers.shift_key();
 
                         let mut delta = None;
 
@@ -4834,14 +5041,22 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             self.sync_cursor_and_selection();
                             changed = true;
                         } else {
-                            if let Key::Character(s) = &event.logical_key {
+                            if event.logical_key == Key::Named(NamedKey::Delete) {
+                                if is_plain_key && self.focused_pane == LEFT_MENUBAR_IDX {
+                                    if let Some(slot_idx) = self.graph().selected_node() {
+                                        if self.delete_node(slot_idx) {
+                                            changed = true;
+                                        }
+                                    }
+                                }
+                            } else if let Key::Character(s) = &event.logical_key {
                                 if is_plain_key {
                                     match s.as_str() {
                                         "e" | "E" => {
-                                            if self.focused_widget == Some(CONTENT_IDX) {
+                                            if self.focused_pane == LEFT_MENUBAR_IDX {
                                                 if let Some(slot_idx) = self.graph().selected_node() {
                                                     let dir = self.current_dir();
-                                                    if slot_idx < dir.children.len() {
+                                                    if slot_idx < dir.children.len() && dir.children[slot_idx].node_type != "utility" {
                                                         let visible = !dir.children[slot_idx].geometry_visible;
                                                         self.current_dir_mut().children[slot_idx].geometry_visible = visible;
                                                         self.sync_nodes();
@@ -4851,6 +5066,13 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                                 }
                                             }
                                         }
+                                        "u" | "U" => {
+                                            if self.focused_pane == LEFT_MENUBAR_IDX {
+                                                if self.move_up() {
+                                                    changed = true;
+                                                }
+                                            }
+                                        }
                                         "r" | "R" => {
                                             if self.focused_pane == RIGHT_MENUBAR_IDX {
                                                 if self.active_camera != "Default Camera" {
@@ -4869,26 +5091,15 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                                 changed = true;
                                             }
                                         }
-                                        "u" | "U" => {
-                                            if self.focused_pane == LEFT_MENUBAR_IDX {
-                                                if !self.current_path.is_empty() {
-                                                    self.current_path.pop();
-                                                    self.on_path_changed();
-                                                    changed = true;
-                                                }
-                                            }
-                                        }
                                         "i" | "I" => {
                                             if self.focused_pane == LEFT_MENUBAR_IDX {
-                                                if self.focused_widget == Some(CONTENT_IDX) {
-                                                    if let Some(slot_idx) = self.graph().selected_node() {
-                                                        let dir = self.current_dir();
-                                                         if slot_idx < dir.children.len() && (dir.children[slot_idx].node_type == "node" || dir.children[slot_idx].node_type == "opencl" || !dir.children[slot_idx].children.is_empty()) {
-                                                             self.current_path.push(slot_idx);
-                                                             self.on_path_changed();
-                                                             changed = true;
-                                                         }
-                                                    }
+                                                if let Some(slot_idx) = self.graph().selected_node() {
+                                                    let dir = self.current_dir();
+                                                     if slot_idx < dir.children.len() && (dir.children[slot_idx].node_type == "node" || dir.children[slot_idx].node_type == "utility" || !dir.children[slot_idx].children.is_empty()) {
+                                                         self.current_path.push(slot_idx);
+                                                         self.on_path_changed();
+                                                         changed = true;
+                                                     }
                                                 }
                                             }
                                         }
@@ -4991,6 +5202,56 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                         }
                                         _ => {}
                                     }
+                                } else if is_ctrl_only && self.focused_pane == LEFT_MENUBAR_IDX {
+                                    match s.as_str() {
+                                        "c" | "C" => {
+                                            if let Some(slot_idx) = self.graph().selected_node() {
+                                                let dir = self.current_dir();
+                                                if slot_idx < dir.children.len() {
+                                                    self.node_clipboard = Some(dir.children[slot_idx].clone());
+                                                }
+                                            }
+                                        }
+                                        "x" | "X" => {
+                                            if let Some(slot_idx) = self.graph().selected_node() {
+                                                let dir = self.current_dir();
+                                                if slot_idx < dir.children.len() {
+                                                    self.node_clipboard = Some(dir.children[slot_idx].clone());
+                                                    self.delete_node(slot_idx);
+                                                    changed = true;
+                                                }
+                                            }
+                                        }
+                                        "v" | "V" => {
+                                            if let Some(ref clipboard_node) = self.node_clipboard {
+                                                let mut node = clipboard_node.clone();
+                                                fn regenerate_ids(n: &mut FsNode) {
+                                                    n.id = generate_node_id();
+                                                    for child in &mut n.children {
+                                                        regenerate_ids(child);
+                                                    }
+                                                }
+                                                regenerate_ids(&mut node);
+                                                let start_x = self.grid_cursor_col as f32;
+                                                let start_y = self.grid_cursor_row as f32;
+                                                let (nx, ny) = self.find_empty_cell(start_x, start_y, None);
+                                                node.position = (nx, ny);
+                                                self.current_dir_mut().children.push(node);
+                                                self.grid_cursor_col = nx as i32;
+                                                self.grid_cursor_row = ny as i32;
+                                                self.sync_nodes();
+                                                self.sync_cursor_and_selection();
+                                                self.rebuild_positions();
+                                                self.apply_layout();
+                                                self.update_panel_bounds();
+                                                self.rebuild_scene_geometry();
+                                                self.upload_vertices();
+                                                self.viewport_dirty = true;
+                                                changed = true;
+                                            }
+                                        }
+                                        _ => {}
+                                    }
                                 }
                             }
                         }
@@ -5043,6 +5304,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 self.last_config_mod_time = current_mod_time;
                 clear_ui::layout::reload_config();
                 self.update_inertial_settings();
+                self.update_graph_settings_from_config();
                 self.rebuild_positions();
                 self.apply_layout();
                 self.upload_vertices();
@@ -5054,28 +5316,24 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 if let Ok(mod_time) = m.modified() {
                     if Some(mod_time) != self.last_design_mod_time {
                         self.last_design_mod_time = Some(mod_time);
-                        let settings = DesignSettings::load();
-                        self.square_viewport = settings.square_viewport;
-                        self.grid_snap_enabled = settings.grid_snap_enabled;
-                        self.network_grid_visible = settings.network_grid_enabled;
-                        self.grid_size_x = settings.grid_size_x;
-                        self.grid_size_y = settings.grid_size_y;
-                        self.skipped_row_h = settings.skipped_row_h;
-                        self.skipped_col_w = settings.skipped_col_w;
-                        self.grid_thickness = settings.grid_thickness;
-                        self.show_grid = settings.show_grid_enabled;
-                        self.show_cube = settings.show_cube_enabled;
-                        self.show_origin = settings.show_origin_enabled;
-                        self.show_camera_pivot = settings.show_camera_pivot_enabled;
-                        self.viewport_bg_color = settings.viewport_bg_color;
-                        self.node_color = settings.node_color;
-                        self.grid_color = settings.grid_color;
-                        self.origin_size = settings.origin_size;
-                        self.camera_pivot_size = settings.camera_pivot_size;
-                        self.uniform_background = settings.uniform_background;
-                        self.network_opacity = settings.network_opacity;
-                        self.cell_color = settings.cell_color;
-                        self.gap_color = settings.gap_color;
+                         let settings = DesignSettings::load();
+                         self.square_viewport = settings.square_viewport;
+                         self.grid_size_x = settings.grid_size_x;
+                         self.grid_size_y = settings.grid_size_y;
+                         self.skipped_row_h = settings.skipped_row_h;
+                         self.skipped_col_w = settings.skipped_col_w;
+                         self.grid_thickness = settings.grid_thickness;
+                         self.show_grid = settings.show_grid_enabled;
+                         self.show_cube = settings.show_cube_enabled;
+                         self.show_origin = settings.show_origin_enabled;
+                         self.show_camera_pivot = settings.show_camera_pivot_enabled;
+                         self.viewport_bg_color = settings.viewport_bg_color;
+                         self.node_color = settings.node_color;
+                         self.grid_color = settings.grid_color;
+                         self.origin_size = settings.origin_size;
+                         self.camera_pivot_size = settings.camera_pivot_size;
+                         self.cell_color = settings.cell_color;
+                         self.gap_color = settings.gap_color;
 
                         colors::set_node_color([self.node_color[0], self.node_color[1], self.node_color[2], 1.0]);
 
diff --git a/src/geometry.rs b/src/geometry.rs
index 976c17a..03584b1 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -10,6 +10,63 @@ use opencl3::memory::{Buffer as ClBuffer, CL_MEM_READ_WRITE};
 use opencl3::types::{cl_float, cl_int, CL_TRUE};
 use glam::Vec3;
 
+struct SimpleRng {
+    state: u32,
+}
+
+impl SimpleRng {
+    fn new(seed: u32) -> Self {
+        Self { state: if seed == 0 { 1 } else { seed } }
+    }
+    
+    fn next_u32(&mut self) -> u32 {
+        let mut x = self.state;
+        x ^= x << 13;
+        x ^= x >> 17;
+        x ^= x << 5;
+        self.state = x;
+        x
+    }
+    
+    fn next_f32(&mut self) -> f32 {
+        (self.next_u32() as f32) / (u32::MAX as f32)
+    }
+}
+
+#[allow(dead_code)]
+fn ray_triangle_intersect(
+    origin: Vec3,
+    dir: Vec3,
+    v0: Vec3,
+    v1: Vec3,
+    v2: Vec3,
+) -> Option<f32> {
+    let edge1 = v1 - v0;
+    let edge2 = v2 - v0;
+    let h = dir.cross(edge2);
+    let a = edge1.dot(h);
+    if a.abs() < 1e-6 {
+        return None;
+    }
+    let f = 1.0 / a;
+    let s = origin - v0;
+    let u = f * s.dot(h);
+    if u < 0.0 || u > 1.0 {
+        return None;
+    }
+    let q = s.cross(edge1);
+    let v = f * dir.dot(q);
+    if v < 0.0 || u + v > 1.0 {
+        return None;
+    }
+    let t = f * edge2.dot(q);
+    if t > 1e-5 {
+        Some(t)
+    } else {
+        None
+    }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub enum GAttribute {
     Float(f32),
@@ -85,9 +142,7 @@ pub fn cube_vertices() -> Vec<Vertex3D> {
     data.iter().map(|&(p, c)| Vertex3D { position: p, color: c }).collect()
 }
 
-pub fn sphere_vertices(center: Vec3, radius: f32) -> Geometry {
-    let lat_steps = 16;
-    let lon_steps = 24;
+pub fn sphere_vertices_res(center: Vec3, radius: f32, lat_steps: usize, lon_steps: usize) -> Geometry {
     let mut vertices = Vec::new();
 
     for lat in 0..lat_steps {
@@ -112,6 +167,10 @@ pub fn sphere_vertices(center: Vec3, radius: f32) -> Geometry {
     Geometry { vertices }
 }
 
+pub fn sphere_vertices(center: Vec3, radius: f32) -> Geometry {
+    sphere_vertices_res(center, radius, 16, 24)
+}
+
 fn sphere_point(center: Vec3, radius: f32, theta: f32, phi: f32) -> Vec3 {
     center + Vec3::new(
         radius * theta.sin() * phi.cos(),
@@ -255,6 +314,21 @@ pub fn find_node_by_name<'a>(root: &'a FsNode, name: &str) -> Option<&'a FsNode>
     None
 }
 
+pub fn find_parent_node<'a>(root: &'a FsNode, child_id: &str) -> Option<&'a FsNode> {
+    fn visit<'a>(node: &'a FsNode, child_id: &str) -> Option<&'a FsNode> {
+        for child in &node.children {
+            if child.id == child_id {
+                return Some(node);
+            }
+            if let Some(res) = visit(child, child_id) {
+                return Some(res);
+            }
+        }
+        None
+    }
+    visit(root, child_id)
+}
+
 pub fn generate_single_node_geometry(root: &FsNode, target: &FsNode, visited: &mut Vec<String>) -> Option<Geometry> {
     let mut err = None;
     generate_single_node_geometry_with_errors(root, target, visited, &mut err)
@@ -295,13 +369,54 @@ pub fn generate_single_node_geometry_with_errors(
             let py = center.y + t * 0.5 - 0.25;
             let pz = center.z + r * angle.sin();
             let pt_center = Vec3::new(px, py, pz);
-            geom.merge(sphere_vertices(pt_center, 0.02));
+            geom.merge(sphere_vertices_res(pt_center, 0.02, 6, 8));
         }
         Some(geom)
     } else if target.node_type.eq_ignore_ascii_case("transform") {
         resolve_transform_geometry_with_errors(root, target, visited, ocl_error)
+    } else if target.node_type.eq_ignore_ascii_case("scatter") {
+        resolve_scatter_geometry_with_errors(root, target, visited, ocl_error)
     } else if target.node_type.eq_ignore_ascii_case("opencl") {
         resolve_opencl_geometry_with_errors(root, target, visited, ocl_error)
+    } else if target.node_type.eq_ignore_ascii_case("node") {
+        if let Some(output_node) = target.children.iter().find(|c| c.node_type.eq_ignore_ascii_case("output")) {
+            generate_single_node_geometry_with_errors(root, output_node, visited, ocl_error)
+        } else {
+            None
+        }
+    } else if target.node_type.eq_ignore_ascii_case("output") {
+        let input_name = node_param_str(target, "Input", "");
+        if input_name.is_empty() {
+            None
+        } else {
+            let parent_node = find_parent_node(root, &target.id);
+            let input_node = if let Some(parent) = parent_node {
+                parent.children.iter().find(|c| c.name == input_name || c.id == input_name)
+            } else {
+                None
+            };
+            let input_node = input_node.or_else(|| find_node_by_name(root, &input_name));
+            if let Some(node) = input_node {
+                generate_single_node_geometry_with_errors(root, node, visited, ocl_error)
+            } else {
+                None
+            }
+        }
+    } else if target.node_type.eq_ignore_ascii_case("input") {
+        if let Some(parent) = find_parent_node(root, &target.id) {
+            let input_name = node_param_str(parent, "Input", "");
+            if !input_name.is_empty() {
+                if let Some(input_node) = find_node_by_name(root, &input_name) {
+                    generate_single_node_geometry_with_errors(root, input_node, visited, ocl_error)
+                } else {
+                    None
+                }
+            } else {
+                None
+            }
+        } else {
+            None
+        }
     } else {
         None
     };
@@ -336,6 +451,337 @@ pub fn resolve_transform_geometry_with_errors(
     Some(geom)
 }
 
+pub fn resolve_scatter_geometry(root: &FsNode, target: &FsNode, visited: &mut Vec<String>) -> Option<Geometry> {
+    let mut err = None;
+    resolve_scatter_geometry_with_errors(root, target, visited, &mut err)
+}
+
+struct PrecomputedTriangle {
+    v0: Vec3,
+    edge1: Vec3,
+    edge2: Vec3,
+    h: Vec3,
+    f: f32,
+}
+
+pub fn resolve_scatter_geometry_with_errors(
+    root: &FsNode,
+    target: &FsNode,
+    visited: &mut Vec<String>,
+    ocl_error: &mut Option<String>,
+) -> Option<Geometry> {
+    if visited.contains(&target.id) {
+        return None;
+    }
+    visited.push(target.id.clone());
+
+    let input_name = node_param_str(target, "Input", "");
+    if input_name.is_empty() {
+        visited.pop();
+        return None;
+    }
+    let input_node = match find_node_by_name(root, &input_name) {
+        Some(node) => node,
+        None => {
+            visited.pop();
+            return None;
+        }
+    };
+    let geom = match generate_single_node_geometry_with_errors(root, input_node, visited, ocl_error) {
+        Some(g) => g,
+        None => {
+            visited.pop();
+            return None;
+        }
+    };
+
+    let num_points = node_param_f32(target, "Points", 100.0) as usize;
+    let radius = node_param_f32(target, "Radius", 0.02);
+
+    let ray_dir = Vec3::new(0.19, 0.98, 0.05).normalize();
+    let mut triangles = Vec::new();
+    let mut min_pos = Vec3::splat(f32::MAX);
+    let mut max_pos = Vec3::splat(f32::MIN);
+
+    for chunk in geom.vertices.chunks_exact(3) {
+        let v0 = Vec3::from_array(chunk[0].pos);
+        let v1 = Vec3::from_array(chunk[1].pos);
+        let v2 = Vec3::from_array(chunk[2].pos);
+
+        min_pos = min_pos.min(v0).min(v1).min(v2);
+        max_pos = max_pos.max(v0).max(v1).max(v2);
+
+        let edge1 = v1 - v0;
+        let edge2 = v2 - v0;
+        let h = ray_dir.cross(edge2);
+        let a = edge1.dot(h);
+        if a.abs() >= 1e-6 {
+            let f = 1.0 / a;
+            triangles.push(PrecomputedTriangle {
+                v0,
+                edge1,
+                edge2,
+                h,
+                f,
+            });
+        }
+    }
+
+    let res = if triangles.is_empty() {
+        Geometry::new()
+    } else {
+        let mut rng = SimpleRng::new(1337);
+        let mut scattered_geom = Geometry::new();
+        let mut found_count = 0;
+        let max_attempts = (num_points * 100).max(10_000);
+
+        for _ in 0..max_attempts {
+            if found_count >= num_points {
+                break;
+            }
+            let rx = min_pos.x + rng.next_f32() * (max_pos.x - min_pos.x);
+            let ry = min_pos.y + rng.next_f32() * (max_pos.y - min_pos.y);
+            let rz = min_pos.z + rng.next_f32() * (max_pos.z - min_pos.z);
+            let candidate = Vec3::new(rx, ry, rz);
+
+            let mut intersection_count = 0;
+            for tri in &triangles {
+                let s = candidate - tri.v0;
+                let u = tri.f * s.dot(tri.h);
+                if u < 0.0 || u > 1.0 {
+                    continue;
+                }
+                let q = s.cross(tri.edge1);
+                let v = tri.f * ray_dir.dot(q);
+                if v < 0.0 || u + v > 1.0 {
+                    continue;
+                }
+                let t = tri.f * tri.edge2.dot(q);
+                if t > 1e-5 {
+                    intersection_count += 1;
+                }
+            }
+
+            if intersection_count % 2 == 1 {
+                scattered_geom.merge(sphere_vertices_res(candidate, radius, 6, 8));
+                found_count += 1;
+            }
+        }
+        scattered_geom
+    };
+
+    visited.pop();
+    Some(res)
+}
+
+fn rewrite_kernel_signature(code: &str) -> String {
+    let bytes = code.as_bytes();
+    if let Some(process_idx) = code.find("process") {
+        let mut idx = process_idx + "process".len();
+        while idx < bytes.len() && (bytes[idx] as char).is_whitespace() {
+            idx += 1;
+        }
+        if idx < bytes.len() && bytes[idx] == b'(' {
+            let start_args = idx + 1;
+            let mut paren_count = 1;
+            let mut end_args = start_args;
+            while end_args < bytes.len() && paren_count > 0 {
+                if bytes[end_args] == b'(' {
+                    paren_count += 1;
+                } else if bytes[end_args] == b')' {
+                    paren_count -= 1;
+                }
+                end_args += 1;
+            }
+            if paren_count == 0 {
+                let closing_paren_idx = end_args - 1;
+                let before = &code[..closing_paren_idx];
+                let after = &code[closing_paren_idx..];
+                let args_str = &code[start_args..closing_paren_idx].trim();
+                let insertion = if args_str.is_empty() {
+                    "__global const float* param_values"
+                } else {
+                    ", __global const float* param_values"
+                };
+                return format!("{}{}{}", before, insertion, after);
+            }
+        }
+    }
+    code.to_string()
+}
+
+pub fn preprocess_opencl_code(code: &str) -> String {
+    let parsed_params = parse_dynamic_params(code);
+    if parsed_params.is_empty() {
+        return code.to_string();
+    }
+
+    let mut param_indices = std::collections::HashMap::new();
+    let mut flat_idx = 0;
+    for p in &parsed_params {
+        param_indices.insert(p.name.clone(), flat_idx);
+        if p.param_type == "float3" {
+            flat_idx += 3;
+        } else {
+            flat_idx += 1;
+        }
+    }
+
+    let mut processed = rewrite_kernel_signature(code);
+
+    let prefixes = [("chf", "slider"), ("chi", "spinbox"), ("chv", "float3"), ("chb", "toggle")];
+    for &(prefix, _) in &prefixes {
+        let pattern = format!("{}(", prefix);
+        while let Some(pos) = processed.find(&pattern) {
+            let start_idx = pos + pattern.len();
+            let mut paren_count = 1;
+            let mut end_pos = start_idx;
+            let bytes = processed.as_bytes();
+            while end_pos < bytes.len() && paren_count > 0 {
+                if bytes[end_pos] == b'(' {
+                    paren_count += 1;
+                } else if bytes[end_pos] == b')' {
+                    paren_count -= 1;
+                }
+                end_pos += 1;
+            }
+            if paren_count == 0 {
+                let full_match = &processed[pos..end_pos];
+                let args_str = &processed[start_idx..end_pos - 1];
+                let mut replacement = None;
+                if let Some(first_quote_pos) = args_str.find(|c| c == '"' || c == '\'') {
+                    let quote_char = args_str.chars().nth(first_quote_pos).unwrap();
+                    if let Some(second_quote_pos) = args_str[first_quote_pos + 1..].find(quote_char) {
+                        let name = &args_str[first_quote_pos + 1..first_quote_pos + 1 + second_quote_pos];
+                        if !name.is_empty() {
+                            if let Some(&flat_idx) = param_indices.get(name) {
+                                match prefix {
+                                    "chf" => {
+                                        replacement = Some(format!("param_values[{}]", flat_idx));
+                                    }
+                                    "chi" | "chb" => {
+                                        replacement = Some(format!("((int)param_values[{}])", flat_idx));
+                                    }
+                                    "chv" => {
+                                        replacement = Some(format!(
+                                            "(float3)(param_values[{}], param_values[{}], param_values[{}])",
+                                            flat_idx, flat_idx + 1, flat_idx + 2
+                                        ));
+                                    }
+                                    _ => {}
+                                }
+                            }
+                        }
+                    }
+                }
+                if let Some(rep) = replacement {
+                    processed = processed.replace(full_match, &rep);
+                } else {
+                    processed = processed.replace(full_match, "0");
+                }
+            } else {
+                break;
+            }
+        }
+    }
+    processed
+}
+
+pub fn parse_dynamic_params(code: &str) -> Vec<ParamDef> {
+    let mut parsed = Vec::new();
+    let prefixes = [("chf", "slider"), ("chi", "spinbox"), ("chv", "float3"), ("chb", "toggle")];
+    for &(prefix, ptype) in &prefixes {
+        let pattern = format!("{}(", prefix);
+        let mut start_idx = 0;
+        while let Some(pos) = code[start_idx..].find(&pattern) {
+            let actual_pos = start_idx + pos;
+            start_idx = actual_pos + pattern.len();
+            let mut paren_count = 1;
+            let mut end_pos = start_idx;
+            let code_bytes = code.as_bytes();
+            while end_pos < code_bytes.len() && paren_count > 0 {
+                if code_bytes[end_pos] == b'(' {
+                    paren_count += 1;
+                } else if code_bytes[end_pos] == b')' {
+                    paren_count -= 1;
+                }
+                end_pos += 1;
+            }
+            if paren_count == 0 {
+                let args_str = &code[start_idx..end_pos - 1];
+                if let Some(first_quote_pos) = args_str.find(|c| c == '"' || c == '\'') {
+                    let quote_char = args_str.chars().nth(first_quote_pos).unwrap();
+                    if let Some(second_quote_pos) = args_str[first_quote_pos + 1..].find(quote_char) {
+                        let name = &args_str[first_quote_pos + 1..first_quote_pos + 1 + second_quote_pos];
+                        if !name.is_empty() {
+                            let mut default_val = match prefix {
+                                "chf" => "0.5".to_string(),
+                                "chi" => "0".to_string(),
+                                "chb" => "false".to_string(),
+                                "chv" => "0.00:0.00:0.00".to_string(),
+                                _ => "".to_string(),
+                            };
+                            let rest = &args_str[first_quote_pos + 1 + second_quote_pos + 1..];
+                            if let Some(comma_pos) = rest.find(',') {
+                                let val_part = rest[comma_pos + 1..].trim();
+                                if !val_part.is_empty() {
+                                    let mut clean_val = val_part.to_string();
+                                    if clean_val.ends_with('f') {
+                                        clean_val.pop();
+                                    }
+                                    if clean_val.ends_with("f32") {
+                                        clean_val.truncate(clean_val.len() - 3);
+                                    }
+                                    let clean_val = clean_val.trim();
+                                    if prefix == "chv" {
+                                        let parts: Vec<String> = val_part.split(',')
+                                            .map(|p| {
+                                                let mut s = p.trim().to_string();
+                                                if s.ends_with('f') { s.pop(); }
+                                                if s.ends_with("f32") { s.truncate(s.len() - 3); }
+                                                s.trim().to_string()
+                                            })
+                                            .collect();
+                                        if parts.len() >= 3 {
+                                            if let (Ok(x), Ok(y), Ok(z)) = (parts[0].parse::<f32>(), parts[1].parse::<f32>(), parts[2].parse::<f32>()) {
+                                                default_val = format!("{:.2}:{:.2}:{:.2}", x, y, z);
+                                            }
+                                        } else {
+                                            if let Ok(val) = clean_val.parse::<f32>() {
+                                                default_val = format!("{:.2}:{:.2}:{:.2}", val, val, val);
+                                            }
+                                        }
+                                    } else {
+                                        default_val = clean_val.to_string();
+                                    }
+                                }
+                            }
+                            if !parsed.iter().any(|p: &ParamDef| p.name == name) {
+                                let (min, max, step) = match prefix {
+                                    "chf" => (Some(0.0), Some(2.0), Some(0.01)),
+                                    "chi" => (Some(0.0), Some(1000.0), Some(1.0)),
+                                    _ => (None, None, None),
+                                };
+                                parsed.push(ParamDef {
+                                    name: name.to_string(),
+                                    label: String::new(),
+                                    param_type: ptype.to_string(),
+                                    default: default_val,
+                                    options: Vec::new(),
+                                    min,
+                                    max,
+                                    step,
+                                });
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    parsed
+}
+
 pub fn resolve_opencl_geometry_with_errors(
     root: &FsNode,
     target: &FsNode,
@@ -344,14 +790,49 @@ pub fn resolve_opencl_geometry_with_errors(
 ) -> Option<Geometry> {
     let input_name = node_param_str(target, "Input", "");
     let mut geom = if !input_name.is_empty() {
-        let input_node = find_node_by_name(root, &input_name)?;
-        generate_single_node_geometry_with_errors(root, input_node, visited, ocl_error)?
+        if let Some(input_node) = find_node_by_name(root, &input_name) {
+            generate_single_node_geometry_with_errors(root, input_node, visited, ocl_error).unwrap_or_default()
+        } else {
+            Geometry::default()
+        }
     } else {
         Geometry::default()
     };
     let code = node_param_str(target, "Code", "");
     if !code.is_empty() {
-        if let Err(e) = run_opencl_kernel(&code, &mut geom) {
+        let parsed_params = parse_dynamic_params(&code);
+        let mut flat_values = Vec::new();
+        for p in &parsed_params {
+            let mut val_str = node_param_str(target, &p.name, &p.default);
+            if !target.params.iter().any(|p_def| p_def.name.eq_ignore_ascii_case(&p.name)) {
+                if let Some(parent) = find_parent_node(root, &target.id) {
+                    val_str = node_param_str(parent, &p.name, &val_str);
+                }
+            }
+            if p.param_type == "float3" {
+                let parts: Vec<&str> = val_str.split(':').collect();
+                let (x, y, z) = if parts.len() >= 3 {
+                    (parts[0].parse::<f32>().unwrap_or(0.0), parts[1].parse::<f32>().unwrap_or(0.0), parts[2].parse::<f32>().unwrap_or(0.0))
+                } else {
+                    (0.0, 0.0, 0.0)
+                };
+                flat_values.push(x);
+                flat_values.push(y);
+                flat_values.push(z);
+            } else {
+                let val = if val_str.eq_ignore_ascii_case("true") {
+                    1.0
+                } else if val_str.eq_ignore_ascii_case("false") {
+                    0.0
+                } else {
+                    val_str.parse::<f32>().unwrap_or(0.0)
+                };
+                flat_values.push(val);
+            }
+        }
+
+        let processed_code = preprocess_opencl_code(&code);
+        if let Err(e) = run_opencl_kernel_with_params(&processed_code, &mut geom, &flat_values) {
             if ocl_error.is_none() {
                 *ocl_error = Some(e);
             }
@@ -407,6 +888,10 @@ fn init_opencl() -> Option<OpenClCache> {
 }
 
 pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String> {
+    run_opencl_kernel_with_params(code, geom, &[])
+}
+
+pub fn run_opencl_kernel_with_params(code: &str, geom: &mut Geometry, params: &[f32]) -> Result<(), String> {
     let is_generator = code.contains("out_count");
     if geom.vertices.is_empty() && !is_generator {
         return Ok(());
@@ -434,6 +919,20 @@ pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String>
     let context = &cache.context;
     let queue = &cache.queue;
 
+    // Prepare parameter values buffer
+    let mut param_values_data = params.to_vec();
+    if param_values_data.is_empty() {
+        param_values_data.push(0.0);
+    }
+    let mut param_values_buf = unsafe {
+        ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, param_values_data.len(), std::ptr::null_mut())
+            .map_err(|e| format!("Failed to create param_values buffer: {:?}", e))?
+    };
+    let _write_param_event = unsafe {
+        queue.enqueue_write_buffer(&mut param_values_buf, CL_TRUE, 0, &param_values_data, &[])
+            .map_err(|e| format!("Failed to write param_values buffer: {:?}", e))?
+    };
+
     if is_generator {
         let in_count = geom.vertices.len();
         let max_vertices = 200_000;
@@ -448,21 +947,23 @@ pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String>
 
         // Create GPU buffers for inputs
         let mut in_pos_buf = unsafe {
-            ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, in_count * 3, std::ptr::null_mut())
+            ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, (in_count * 3).max(1), std::ptr::null_mut())
                 .map_err(|e| format!("Failed to create input positions buffer: {:?}", e))?
         };
         let mut in_col_buf = unsafe {
-            ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, in_count * 3, std::ptr::null_mut())
+            ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, (in_count * 3).max(1), std::ptr::null_mut())
                 .map_err(|e| format!("Failed to create input colors buffer: {:?}", e))?
         };
 
         // Write input data to GPU
         let _write_pos_event = unsafe {
-            queue.enqueue_write_buffer(&mut in_pos_buf, CL_TRUE, 0, &in_pos_data, &[])
+            let write_data = if in_pos_data.is_empty() { &[0.0f32] } else { &in_pos_data[..] };
+            queue.enqueue_write_buffer(&mut in_pos_buf, CL_TRUE, 0, write_data, &[])
                 .map_err(|e| format!("Failed to write input positions buffer: {:?}", e))?
         };
         let _write_col_event = unsafe {
-            queue.enqueue_write_buffer(&mut in_col_buf, CL_TRUE, 0, &in_col_data, &[])
+            let write_data = if in_col_data.is_empty() { &[0.0f32] } else { &in_col_data[..] };
+            queue.enqueue_write_buffer(&mut in_col_buf, CL_TRUE, 0, write_data, &[])
                 .map_err(|e| format!("Failed to write input colors buffer: {:?}", e))?
         };
 
@@ -489,16 +990,22 @@ pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String>
 
         // Execute kernel
         let global_work_size = if in_count == 0 { 1 } else { in_count };
+        let mut exec = ExecuteKernel::new(kernel);
         let kernel_event = unsafe {
-            ExecuteKernel::new(kernel)
-                .set_arg(&in_pos_buf)
+            exec.set_arg(&in_pos_buf)
                 .set_arg(&in_col_buf)
                 .set_arg(&(in_count as cl_int))
                 .set_arg(&out_pos_buf)
                 .set_arg(&out_col_buf)
                 .set_arg(&out_count_buf)
-                .set_arg(&(max_vertices as cl_int))
-                .set_global_work_size(global_work_size)
+                .set_arg(&(max_vertices as cl_int));
+
+            let num_args = kernel.num_args().unwrap_or(0);
+            if num_args >= 8 {
+                exec.set_arg(&param_values_buf);
+            }
+
+            exec.set_global_work_size(global_work_size)
                 .enqueue_nd_range(&queue)
                 .map_err(|e| format!("Failed to enqueue kernel: {:?}", e))?
         };
@@ -571,12 +1078,18 @@ pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String>
         };
 
         // Execute kernel
+        let mut exec = ExecuteKernel::new(kernel);
         let kernel_event = unsafe {
-            ExecuteKernel::new(kernel)
-                .set_arg(&pos_buf)
+            exec.set_arg(&pos_buf)
                 .set_arg(&col_buf)
-                .set_arg(&(count as cl_int))
-                .set_global_work_size(count)
+                .set_arg(&(count as cl_int));
+
+            let num_args = kernel.num_args().unwrap_or(0);
+            if num_args >= 4 {
+                exec.set_arg(&param_values_buf);
+            }
+
+            exec.set_global_work_size(count)
                 .enqueue_nd_range(&queue)
                 .map_err(|e| format!("Failed to enqueue kernel: {:?}", e))?
         };
@@ -603,24 +1116,38 @@ pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String>
     Ok(())
 }
 
+pub fn is_geometry_node_type(node_type: &str) -> bool {
+    let nt = node_type.to_lowercase();
+    nt == "sphere"
+        || nt == "line"
+        || nt == "add"
+        || nt == "transform"
+        || nt == "opencl"
+        || nt == "box"
+        || nt == "input"
+        || nt == "output"
+        || nt == "scatter"
+}
+
 pub fn network_sphere_vertices(root: &FsNode) -> Geometry {
     let mut err = None;
     network_sphere_vertices_with_errors(root, &mut err)
 }
 
 pub fn network_sphere_vertices_with_errors(root: &FsNode, ocl_error: &mut Option<String>) -> Geometry {
-    fn visit(root: &FsNode, node: &FsNode, count: &mut usize, out: &mut Geometry, ocl_error: &mut Option<String>) {
+    fn visit(root: &FsNode, node: &FsNode, parent_visible: bool, count: &mut usize, out: &mut Geometry, ocl_error: &mut Option<String>) {
+        let is_visible = parent_visible && node.geometry_visible;
         if node.node_type.eq_ignore_ascii_case("sphere") {
             let idx = *count;
             *count += 1;
-            if node.geometry_visible {
+            if is_visible {
                 let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
                 out.merge(sphere_vertices(center, node_param_f32(node, "Radius", 0.5).max(0.05)));
             }
         } else if node.node_type.eq_ignore_ascii_case("line") {
             let idx = *count;
             *count += 1;
-            if node.geometry_visible {
+            if is_visible {
                 let start = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
                 let length = node_param_f32(node, "Length", 1.0);
                 let thickness = node_param_f32(node, "Thickness", 0.02);
@@ -630,7 +1157,7 @@ pub fn network_sphere_vertices_with_errors(root: &FsNode, ocl_error: &mut Option
         } else if node.node_type.eq_ignore_ascii_case("add") {
             let idx = *count;
             *count += 1;
-            if node.geometry_visible {
+            if is_visible {
                 let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
                 let num_points = node_param_f32(node, "Points", 100.0) as i32;
                 for i in 0..num_points {
@@ -641,22 +1168,31 @@ pub fn network_sphere_vertices_with_errors(root: &FsNode, ocl_error: &mut Option
                     let py = center.y + t * 0.5 - 0.25;
                     let pz = center.z + r * angle.sin();
                     let pt_center = Vec3::new(px, py, pz);
-                    out.merge(sphere_vertices(pt_center, 0.02));
+                    out.merge(sphere_vertices_res(pt_center, 0.02, 6, 8));
                 }
             }
         } else if node.node_type.eq_ignore_ascii_case("transform") {
-            let idx = *count;
+            let _idx = *count;
             *count += 1;
-            if node.geometry_visible {
+            if is_visible {
                 let mut visited = Vec::new();
                 if let Some(geom) = resolve_transform_geometry_with_errors(root, node, &mut visited, ocl_error) {
                     out.merge(geom);
                 }
             }
+        } else if node.node_type.eq_ignore_ascii_case("scatter") {
+            let _idx = *count;
+            *count += 1;
+            if is_visible {
+                let mut visited = Vec::new();
+                if let Some(geom) = resolve_scatter_geometry_with_errors(root, node, &mut visited, ocl_error) {
+                    out.merge(geom);
+                }
+            }
         } else if node.node_type.eq_ignore_ascii_case("opencl") {
-            let idx = *count;
+            let _idx = *count;
             *count += 1;
-            if node.geometry_visible {
+            if is_visible {
                 let mut visited = Vec::new();
                 if let Some(geom) = resolve_opencl_geometry_with_errors(root, node, &mut visited, ocl_error) {
                     out.merge(geom);
@@ -664,14 +1200,14 @@ pub fn network_sphere_vertices_with_errors(root: &FsNode, ocl_error: &mut Option
             }
         }
         for child in &node.children {
-            visit(root, child, count, out, ocl_error);
+            visit(root, child, is_visible, count, out, ocl_error);
         }
     }
 
     let mut out = Geometry::new();
     let mut count = 0;
     for child in &root.children {
-        visit(root, child, &mut count, &mut out, ocl_error);
+        visit(root, child, true, &mut count, &mut out, ocl_error);
     }
     out
 }
@@ -683,7 +1219,8 @@ pub fn find_sphere_index(root: &FsNode, target: &FsNode) -> Option<usize> {
             || node.node_type.eq_ignore_ascii_case("line") 
             || node.node_type.eq_ignore_ascii_case("add")
             || node.node_type.eq_ignore_ascii_case("transform")
-            || node.node_type.eq_ignore_ascii_case("opencl") {
+            || node.node_type.eq_ignore_ascii_case("opencl")
+            || node.node_type.eq_ignore_ascii_case("scatter") {
             let idx = *count;
             *count += 1;
             if is_target {
@@ -1007,6 +1544,9 @@ mod tests {
     #[test]
     fn test_add_node_points() {
         let add_node = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Add points test".to_string(),
             node_type: "add".to_string(),
             children: vec![],
@@ -1026,6 +1566,9 @@ mod tests {
             position: (0.0, 0.0),
         };
         let root = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "root".to_string(),
             node_type: "node".to_string(),
             children: vec![add_node],
@@ -1034,12 +1577,15 @@ mod tests {
             position: (0.0, 0.0),
         };
         let geom = network_sphere_vertices(&root);
-        assert_eq!(geom.vertices.len(), 5 * 2304);
+        assert_eq!(geom.vertices.len(), 5 * 288);
     }
 
     #[test]
     fn test_transform_node() {
         let sphere = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Sphere 1".to_string(),
             node_type: "sphere".to_string(),
             children: vec![],
@@ -1059,6 +1605,9 @@ mod tests {
             position: (0.0, 0.0),
         };
         let transform1 = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Transform 1".to_string(),
             node_type: "transform".to_string(),
             children: vec![],
@@ -1088,6 +1637,9 @@ mod tests {
             position: (0.0, 0.0),
         };
         let root = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "root".to_string(),
             node_type: "node".to_string(),
             children: vec![sphere.clone(), transform1.clone()],
@@ -1109,6 +1661,9 @@ mod tests {
 
         // Test chained transform
         let transform2 = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Transform 2".to_string(),
             node_type: "transform".to_string(),
             children: vec![],
@@ -1138,6 +1693,9 @@ mod tests {
             position: (0.0, 0.0),
         };
         let root_chained = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "root".to_string(),
             node_type: "node".to_string(),
             children: vec![sphere, transform1, transform2.clone()],
@@ -1156,6 +1714,9 @@ mod tests {
 
         // Test loop detection
         let transform_loop = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Transform Loop".to_string(),
             node_type: "transform".to_string(),
             children: vec![],
@@ -1185,6 +1746,9 @@ mod tests {
             position: (0.0, 0.0),
         };
         let root_loop = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "root".to_string(),
             node_type: "node".to_string(),
             children: vec![transform_loop.clone()],
@@ -1205,6 +1769,9 @@ mod tests {
         }
 
         let sphere = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "Sphere 1".to_string(),
             node_type: "sphere".to_string(),
             children: vec![],
@@ -1225,6 +1792,9 @@ mod tests {
         };
 
         let opencl_node = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "OpenCL 1".to_string(),
             node_type: "opencl".to_string(),
             children: vec![],
@@ -1262,6 +1832,9 @@ mod tests {
         };
 
         let root = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
             name: "root".to_string(),
             node_type: "node".to_string(),
             children: vec![sphere, opencl_node.clone()],
@@ -1280,6 +1853,108 @@ mod tests {
         let avg_y = geom.vertices.iter().map(|v| v.pos[1]).sum::<f32>() / geom.vertices.len() as f32;
         assert!((avg_y - 2.55).abs() < 0.01);
     }
+
+    #[test]
+    fn test_scatter_node() {
+        let sphere = FsNode {
+            id: "sphere1".to_string(),
+            inputs: 1,
+            outputs: 1,
+            name: "Sphere 1".to_string(),
+            node_type: "sphere".to_string(),
+            children: vec![],
+            params: vec![
+                ParamDef {
+                    name: "Radius".to_string(),
+                    label: String::new(),
+                    param_type: "slider".to_string(),
+                    default: "0.5".to_string(),
+                    options: vec![],
+                    min: None,
+                    max: None,
+                    step: None,
+                }
+            ],
+            geometry_visible: true,
+            position: (0.0, 0.0),
+        };
+
+        let scatter = FsNode {
+            id: "scatter1".to_string(),
+            inputs: 1,
+            outputs: 1,
+            name: "Scatter 1".to_string(),
+            node_type: "scatter".to_string(),
+            children: vec![],
+            params: vec![
+                ParamDef {
+                    name: "Input".to_string(),
+                    label: String::new(),
+                    param_type: "text".to_string(),
+                    default: "Sphere 1".to_string(),
+                    options: vec![],
+                    min: None,
+                    max: None,
+                    step: None,
+                },
+                ParamDef {
+                    name: "Points".to_string(),
+                    label: String::new(),
+                    param_type: "spinbox".to_string(),
+                    default: "15".to_string(),
+                    options: vec![],
+                    min: None,
+                    max: None,
+                    step: None,
+                },
+                ParamDef {
+                    name: "Radius".to_string(),
+                    label: String::new(),
+                    param_type: "slider".to_string(),
+                    default: "0.02".to_string(),
+                    options: vec![],
+                    min: None,
+                    max: None,
+                    step: None,
+                }
+            ],
+            geometry_visible: true,
+            position: (0.0, 0.0),
+        };
+
+        let root = FsNode {
+            id: String::new(),
+            inputs: 1,
+            outputs: 1,
+            name: "root".to_string(),
+            node_type: "node".to_string(),
+            children: vec![sphere, scatter.clone()],
+            params: vec![],
+            geometry_visible: true,
+            position: (0.0, 0.0),
+        };
+
+        let mut visited = Vec::new();
+        let geom = resolve_scatter_geometry(&root, &scatter, &mut visited).unwrap();
+
+        // 15 scattered spheres. Each sphere with lat_steps=6, lon_steps=8 has:
+        // 6 * 8 = 48 quads. Each quad has 6 vertices. 48 * 6 = 288 vertices.
+        // 15 * 288 = 4320 vertices.
+        assert_eq!(geom.vertices.len(), 15 * 288);
+
+        // Center of sphere at idx 0 is Vec3::new(-1.875, 0.55, 0.0). Radius = 0.5.
+        // Let's check that each scattered sphere's center is indeed inside the parent sphere.
+        let center = Vec3::new(-1.875, 0.55, 0.0);
+        for chunk in geom.vertices.chunks_exact(288) {
+            let mut sum = Vec3::ZERO;
+            for v in chunk {
+                sum += Vec3::from_array(v.pos);
+            }
+            let avg = sum / 288.0;
+            let dist = avg.distance(center);
+            assert!(dist <= 0.5, "Scattered point center {:?} (distance {}) is outside the sphere of radius 0.5", avg, dist);
+        }
+    }
 }
 
 
diff --git a/src/main.rs b/src/main.rs
index 8fdad18..c6389d6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -50,7 +50,7 @@ pub mod project;
 pub mod render;
 pub mod shortcut;
 
-use app::{State, CustomEvent, HttpAction, ModifiersState, TouchPhase, DesignSettings, param_display};
+use app::{State, CustomEvent, HttpAction, ModifiersState, TouchPhase, DesignSettings, param_display, FsNode, Project, ProjectViewState, ParamDef};
 use window::{AppState, WindowEvent};
 use api::start_http_server;
 use geometry::*;
@@ -266,7 +266,11 @@ fn main() {
     const KEY_REPEAT_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
 
     loop {
-        let timeout = std::time::Duration::from_millis(16);
+        let timeout = if app.redraw {
+            std::time::Duration::ZERO
+        } else {
+            std::time::Duration::from_millis(16)
+        };
         event_loop.dispatch(timeout, &mut app).unwrap();
 
         if app.exit || app.state.as_ref().map(|s| s.exit_requested).unwrap_or(false) {
@@ -390,6 +394,8 @@ fn create_memfd_with_data(name: &str, data: &[u8]) -> std::io::Result<std::os::u
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::app::{get_next_visible_pane, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX};
+    use crate::shortcut::{Shortcut, ShortcutManager, Action};
 
     #[test]
     fn test_load_default_project() {
@@ -399,33 +405,230 @@ mod tests {
         assert_eq!(proj.name, "Default Project");
         assert_eq!(proj.view_state.active_camera, "Camera 1");
         assert_eq!(proj.root.name, "root");
-        assert_eq!(proj.root.children.len(), 3);
+        assert_eq!(proj.root.children.len(), 2);
         assert_eq!(proj.root.children[0].name, "Camera 1");
         assert_eq!(proj.root.children[0].position, (1.0, 1.0));
         assert_eq!(proj.root.children[1].name, "Sphere 1");
         assert_eq!(proj.root.children[1].position, (4.0, 2.0));
-        assert_eq!(proj.root.children[2].name, "Transform 1");
-        assert_eq!(proj.root.children[2].position, (4.0, 3.0));
+    }
+
+    #[test]
+    fn test_node_template_names() {
+        let templates_root = crate::app::load_fs_tree();
+        let templates = crate::app::flatten_node_templates(&templates_root);
+        assert!(!templates.is_empty(), "No templates loaded!");
+        for t in templates {
+            let last_char = t.label.chars().last().unwrap();
+            assert!(!last_char.is_ascii_digit(), "Template name '{}' ends with a digit, but templates should not have numeric suffixes in the add node popup.", t.label);
+        }
+    }
+
+    #[test]
+    fn test_subnet_template_child_resolution() {
+        let templates_root = crate::app::load_fs_tree();
+        let box_template = templates_root
+            .children
+            .iter()
+            .find(|t| t.name == "Box")
+            .expect("Box template should be loaded");
+        
+        assert_eq!(box_template.children.len(), 3);
+        
+        let input1 = box_template.children.iter().find(|c| c.name == "input1").unwrap();
+        assert_eq!(input1.node_type, "input");
+        
+        let opencl1 = box_template.children.iter().find(|c| c.name == "opencl1").unwrap();
+        assert_eq!(opencl1.node_type, "opencl");
+        
+        let input_param = opencl1.params.iter().find(|p| p.name == "Input").unwrap();
+        assert_eq!(input_param.default, "input1");
+        
+        let update_param = opencl1.params.iter().find(|p| p.name == "Update Parameters").unwrap();
+        assert_eq!(update_param.param_type, "button");
+        
+        let output1 = box_template.children.iter().find(|c| c.name == "output1").unwrap();
+        assert_eq!(output1.node_type, "output");
+        let output_input = output1.params.iter().find(|p| p.name == "Input").unwrap();
+        assert_eq!(output_input.default, "opencl1");
+    }
+
+    #[test]
+    fn test_sphere_subnet_geometry_generation() {
+        let templates_root = crate::app::load_fs_tree();
+        let sphere_template = templates_root
+            .children
+            .iter()
+            .find(|t| t.name == "Sphere")
+            .expect("Sphere template should be loaded");
+        
+        assert_eq!(sphere_template.children.len(), 2);
+        
+        let opencl1 = sphere_template.children.iter().find(|c| c.name == "opencl1").unwrap();
+        assert_eq!(opencl1.node_type, "opencl");
+        
+        let output1 = sphere_template.children.iter().find(|c| c.name == "output1").unwrap();
+        assert_eq!(output1.node_type, "output");
+        
+        let mut sphere_instance = sphere_template.clone();
+        sphere_instance.id = "sphere_inst".to_string();
+        for child in &mut sphere_instance.children {
+            child.id = format!("{}_{}", sphere_instance.id, child.name);
+        }
+        
+        let root = FsNode {
+            id: "root".to_string(),
+            name: "root".to_string(),
+            node_type: "node".to_string(),
+            children: vec![sphere_instance],
+            params: vec![],
+            geometry_visible: true,
+            position: (0.0, 0.0),
+            inputs: 0,
+            outputs: 0,
+        };
+        
+        let mut visited = Vec::new();
+        let mut ocl_err = None;
+        let geom = crate::geometry::generate_single_node_geometry_with_errors(
+            &root,
+            &root.children[0],
+            &mut visited,
+            &mut ocl_err,
+        ).expect("Geometry generation failed");
+        
+        assert!(ocl_err.is_none(), "OpenCL compilation error: {:?}", ocl_err);
+        assert_eq!(geom.vertices.len(), 2304);
+        
+        let mut max_dist: f32 = 0.0;
+        for v in &geom.vertices {
+            let dx = v.pos[0] - 0.0;
+            let dy = v.pos[1] - 0.55;
+            let dz = v.pos[2] - 0.0;
+            let dist = (dx*dx + dy*dy + dz*dz).sqrt();
+            if dist > max_dist {
+                max_dist = dist;
+            }
+        }
+        assert!((max_dist - 0.5).abs() < 0.01, "Expected radius around 0.5, got {}", max_dist);
+        
+        let mut sphere_instance_2 = sphere_template.clone();
+        sphere_instance_2.id = "sphere_inst_2".to_string();
+        for child in &mut sphere_instance_2.children {
+            child.id = format!("{}_{}", sphere_instance_2.id, child.name);
+        }
+        if let Some(radius_param) = sphere_instance_2.params.iter_mut().find(|p| p.name == "Radius") {
+            radius_param.default = "1.0".to_string();
+        }
+        
+        let root_2 = FsNode {
+            id: "root".to_string(),
+            name: "root".to_string(),
+            node_type: "node".to_string(),
+            children: vec![sphere_instance_2],
+            params: vec![],
+            geometry_visible: true,
+            position: (0.0, 0.0),
+            inputs: 0,
+            outputs: 0,
+        };
+        
+        let mut visited_2 = Vec::new();
+        let mut ocl_err_2 = None;
+        let geom_2 = crate::geometry::generate_single_node_geometry_with_errors(
+            &root_2,
+            &root_2.children[0],
+            &mut visited_2,
+            &mut ocl_err_2,
+        ).expect("Geometry generation failed");
+        
+        assert!(ocl_err_2.is_none(), "OpenCL compilation error: {:?}", ocl_err_2);
+        assert_eq!(geom_2.vertices.len(), 2304);
+        
+        let mut max_dist_2: f32 = 0.0;
+        for v in &geom_2.vertices {
+            let dx = v.pos[0] - 0.0;
+            let dy = v.pos[1] - 0.55;
+            let dz = v.pos[2] - 0.0;
+            let dist = (dx*dx + dy*dy + dz*dz).sqrt();
+            if dist > max_dist_2 {
+                max_dist_2 = dist;
+            }
+        }
+        assert!((max_dist_2 - 1.0).abs() < 0.01, "Expected radius around 1.0, got {}", max_dist_2);
+    }
+
+    #[test]
+    fn test_dynamic_parameters_parsing_and_preprocessing() {
+        let code = r#"
+            float freq = chf("freq", 4.0f);
+            int count = chi("count", 15);
+            float3 col = chv("col", 0.8f, 0.2f, 0.2f);
+            float scale = chf("scale");
+        "#;
+        
+        let parsed = crate::geometry::parse_dynamic_params(code);
+        assert_eq!(parsed.len(), 4);
+        
+        assert_eq!(parsed[0].name, "freq");
+        assert_eq!(parsed[0].param_type, "slider");
+        assert_eq!(parsed[0].default, "4.0");
+        
+        assert_eq!(parsed[1].name, "scale");
+        assert_eq!(parsed[1].param_type, "slider");
+        assert_eq!(parsed[1].default, "0.5");
+        
+        assert_eq!(parsed[2].name, "count");
+        assert_eq!(parsed[2].param_type, "spinbox");
+        assert_eq!(parsed[2].default, "15");
+        
+        assert_eq!(parsed[3].name, "col");
+        assert_eq!(parsed[3].param_type, "float3");
+        assert_eq!(parsed[3].default, "0.80:0.20:0.20");
+        
+        let mut target = FsNode {
+            id: "node1".to_string(),
+            name: "OpenCL Node".to_string(),
+            node_type: "opencl".to_string(),
+            children: Vec::new(),
+            params: parsed,
+            geometry_visible: true,
+            position: (0.0, 0.0),
+            inputs: 1,
+            outputs: 1,
+        };
+        
+        target.params[1].default = "1.25".to_string(); // "scale"
+        let preprocessed = crate::geometry::preprocess_opencl_code(code);
+        assert!(preprocessed.contains("param_values[0]"));
+        assert!(preprocessed.contains("((int)param_values[2])"));
+        assert!(preprocessed.contains("(float3)(param_values[3], param_values[4], param_values[5])"));
+        assert!(preprocessed.contains("param_values[1]"));
     }
 
     #[test]
     fn test_project_serialization_roundtrip() {
         let root = FsNode {
+            id: "root".to_string(),
             name: "test_root".to_string(),
             node_type: "node".to_string(),
             children: vec![
                 FsNode {
+                    id: "child1".to_string(),
                     name: "child1".to_string(),
                     node_type: "sphere".to_string(),
                     children: vec![],
                     params: vec![],
                     geometry_visible: true,
                     position: (5.0, 6.0),
+                    inputs: 0,
+                    outputs: 1,
                 }
             ],
             params: vec![],
             geometry_visible: true,
             position: (0.0, 0.0),
+            inputs: 0,
+            outputs: 0,
         };
         let view_state = ProjectViewState {
             active_camera: "child1".to_string(),
@@ -567,8 +770,6 @@ mod tests {
         assert_eq!(settings.show_camera_pivot_enabled, false);
         assert_eq!(settings.camera_pivot_size, 1.0);
         assert_eq!(settings.grid_color, [0.35, 0.35, 0.40]);
-        assert_eq!(settings.uniform_background, false);
-        assert_eq!(settings.network_opacity, 0.95);
         assert_eq!(settings.cell_color, [0.13, 0.13, 0.16]);
         assert_eq!(settings.gap_color, [0.07, 0.07, 0.09]);
         
@@ -577,8 +778,6 @@ mod tests {
         assert_eq!(settings_roundtrip.show_camera_pivot_enabled, false);
         assert_eq!(settings_roundtrip.camera_pivot_size, 1.0);
         assert_eq!(settings_roundtrip.grid_color, [0.35, 0.35, 0.40]);
-        assert_eq!(settings_roundtrip.uniform_background, false);
-        assert_eq!(settings_roundtrip.network_opacity, 0.95);
         assert_eq!(settings_roundtrip.cell_color, [0.13, 0.13, 0.16]);
         assert_eq!(settings_roundtrip.gap_color, [0.07, 0.07, 0.09]);
     }
diff --git a/src/project.rs b/src/project.rs
index 59a7cd2..8a56a9f 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -71,6 +71,7 @@ impl State {
         self.recent_files.truncate(10);
         Self::save_recent_files(&self.recent_files);
         self.rebuild_recent_buttons();
+        self.ensure_menubar_subnets();
         self.update_paginator();
     }
 
@@ -162,18 +163,9 @@ impl State {
 
             self.sync_grid_settings();
             self.sync_nodes();
-
-            let params = if !self.is_detached_network {
-                self.graph().selected_node().and_then(|sel_idx| {
-                    let dir = self.current_dir();
-                    if sel_idx < dir.children.len() {
-                        Some(param_display(&dir.children[sel_idx].params))
-                    } else { None }
-                }).unwrap_or_default()
-            } else {
-                vec![]
-            };
-            self.param_mut().set_display_params(&params);
+            self.sync_cursor_and_selection_from_loaded();
+            self.sync_cursor_and_selection();
+            self.sync_parameters_pane();
 
             self.rebuild_scene_geometry();
             self.rebuild_positions();
@@ -225,19 +217,10 @@ impl State {
 
         self.sync_grid_settings();
         self.sync_nodes();
-
-        // Sync Parameters pane with selected node
-        let params = if !self.is_detached_network {
-            self.graph().selected_node().and_then(|sel_idx| {
-                let dir = self.current_dir();
-                if sel_idx < dir.children.len() {
-                    Some(param_display(&dir.children[sel_idx].params))
-                } else { None }
-            }).unwrap_or_default()
-        } else {
-            vec![]
-        };
-        self.param_mut().set_display_params(&params);
+        self.sync_cursor_and_selection_from_loaded();
+        self.sync_cursor_and_selection();
+        self.add_recent_file(project_dir.clone());
+        self.sync_parameters_pane();
 
         self.rebuild_scene_geometry();
         self.rebuild_positions();
@@ -260,6 +243,8 @@ impl State {
             params: vec![],
             geometry_visible: true,
             position: (0.0, 0.0),
+            inputs: 0,
+            outputs: 0,
         };
         self.ensure_menubar_subnets();
         self.apply_settings_from_menubar_subnets();
@@ -283,6 +268,8 @@ impl State {
 
         self.sync_grid_settings();
         self.sync_nodes();
+        self.sync_cursor_and_selection();
+        self.sync_parameters_pane();
         self.rebuild_scene_geometry();
         self.rebuild_positions();
         self.apply_layout();
@@ -302,9 +289,16 @@ impl State {
         camera_options.extend(camera_nodes);
         let camera_options_refs: Vec<&str> = camera_options.iter().map(|s| s.as_str()).collect();
 
-        fn find_or_create_subnet<'a>(parent: &'a mut FsNode, name: &str, pos: (f32, f32)) -> &'a mut FsNode {
+        let mut recent_options = vec!["- Select -".to_string()];
+        for path in &self.recent_files {
+            recent_options.push(path.to_string_lossy().to_string());
+        }
+        let recent_options_refs: Vec<&str> = recent_options.iter().map(|s| s.as_str()).collect();
+
+        fn find_or_create_subnet<'a>(parent: &'a mut FsNode, name: &str, node_type: &str, pos: (f32, f32)) -> &'a mut FsNode {
             if let Some(idx) = parent.children.iter().position(|c| c.name == name) {
                 let node = &mut parent.children[idx];
+                node.node_type = node_type.to_string();
                 if node.position == (0.0, 0.0) {
                     node.position = pos;
                 }
@@ -313,11 +307,13 @@ impl State {
                 let new_node = FsNode {
                     id: crate::app::generate_node_id(),
                     name: name.to_string(),
-                    node_type: "node".to_string(),
+                    node_type: node_type.to_string(),
                     children: vec![],
                     params: vec![],
                     geometry_visible: true,
                     position: pos,
+                    inputs: 1,
+                    outputs: 1,
                 };
                 parent.children.push(new_node);
                 parent.children.last_mut().unwrap()
@@ -340,216 +336,172 @@ impl State {
         }
 
         // 1. Main subnet
-        let main_node = find_or_create_subnet(&mut self.fs_root, "Main", (0.0, 0.0));
-        
-        let file_node = find_or_create_subnet(main_node, "File", (0.0, 0.0));
-        ensure_param(file_node, "New Project", "button", "", &[], None, None, None);
-        ensure_param(file_node, "Open", "button", "", &[], None, None, None);
-        ensure_param(file_node, "Save", "button", "", &[], None, None, None);
-        ensure_param(file_node, "Save As", "button", "", &[], None, None, None);
-        ensure_param(file_node, "Exit", "button", "", &[], None, None, None);
-
-        let edit_node = find_or_create_subnet(main_node, "Edit", (2.0, 0.0));
-        ensure_param(edit_node, "Undo", "button", "", &[], None, None, None);
-        ensure_param(edit_node, "Redo", "button", "", &[], None, None, None);
-
-        let view_node = find_or_create_subnet(main_node, "View", (4.0, 0.0));
-        ensure_param(view_node, "Zoom In", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Zoom Out", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Reset Zoom", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Detach Circular Window", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Show Network Pane", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Show Viewport Pane", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Show Parameters Pane", "button", "", &[], None, None, None);
-        ensure_param(view_node, "Show Spreadsheet Pane", "button", "", &[], None, None, None);
-
-        let help_node = find_or_create_subnet(main_node, "Help", (6.0, 0.0));
-        ensure_param(help_node, "About", "button", "", &[], None, None, None);
+        let main_node = find_or_create_subnet(&mut self.fs_root, "Main", "utility", (0.0, 0.0));
+        main_node.children.clear();
+
+        ensure_param(main_node, "File", "section", "", &[], None, None, None);
+        ensure_param(main_node, "New Project", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Open", "button", "", &[], None, None, None);
+
+        if let Some(p) = main_node.params.iter_mut().find(|p| p.name == "Open Recent") {
+            p.options = recent_options.clone();
+            if !p.options.contains(&p.default) {
+                p.default = "- Select -".to_string();
+            }
+        } else {
+            ensure_param(main_node, "Open Recent", "choice", "- Select -", &recent_options_refs, None, None, None);
+        }
+
+        ensure_param(main_node, "Save", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Save As", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Exit", "button", "", &[], None, None, None);
+
+        ensure_param(main_node, "Edit", "section", "", &[], None, None, None);
+        ensure_param(main_node, "Undo", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Redo", "button", "", &[], None, None, None);
+
+        ensure_param(main_node, "View", "section", "", &[], None, None, None);
+        ensure_param(main_node, "Zoom In", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Zoom Out", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Reset Zoom", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Detach Circular Window", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Show Network Pane", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Show Viewport Pane", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Show Parameters Pane", "button", "", &[], None, None, None);
+        ensure_param(main_node, "Show Spreadsheet Pane", "button", "", &[], None, None, None);
+
+        ensure_param(main_node, "Help", "section", "", &[], None, None, None);
+        ensure_param(main_node, "About", "button", "", &[], None, None, None);
 
         // 2. Network subnet
-        let net_node = find_or_create_subnet(&mut self.fs_root, "Network", (2.0, 0.0));
-
-        let net_file_node = find_or_create_subnet(net_node, "File", (0.0, 0.0));
-        ensure_param(net_file_node, "New", "button", "", &[], None, None, None);
-        ensure_param(net_file_node, "Open", "button", "", &[], None, None, None);
-        ensure_param(net_file_node, "Save", "button", "", &[], None, None, None);
-        ensure_param(net_file_node, "Save As", "button", "", &[], None, None, None);
-
-        let net_edit_node = find_or_create_subnet(net_node, "Edit", (2.0, 0.0));
-        ensure_param(net_edit_node, "Undo", "button", "", &[], None, None, None);
-        ensure_param(net_edit_node, "Redo", "button", "", &[], None, None, None);
-
-        let net_view_node = find_or_create_subnet(net_node, "View", (4.0, 0.0));
-        ensure_param(net_view_node, "Zoom In", "button", "", &[], None, None, None);
-        ensure_param(net_view_node, "Zoom Out", "button", "", &[], None, None, None);
-        ensure_param(net_view_node, "Circular Pane", "choice", if self.circular_network_pane { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(net_view_node, "Detach Pane", "button", "", &[], None, None, None);
-        ensure_param(net_view_node, "Close Pane", "button", "", &[], None, None, None);
-
-        let net_settings_node = find_or_create_subnet(net_node, "Settings", (6.0, 0.0));
-        ensure_param(net_settings_node, "Snap to Grid", "choice", if self.grid_snap_enabled { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(net_settings_node, "Grid Visible", "choice", if self.network_grid_visible { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(net_settings_node, "Uniform Background", "choice", if self.uniform_background { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(net_settings_node, "Opacity", "slider", &format!("{:.2}", self.network_opacity), &[], Some(0.0), Some(1.0), None);
-        ensure_param(net_settings_node, "Node Color R", "spinbox", &((self.node_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Node Color G", "spinbox", &((self.node_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Node Color B", "spinbox", &((self.node_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Cell Color R", "spinbox", &((self.cell_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Cell Color G", "spinbox", &((self.cell_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Cell Color B", "spinbox", &((self.cell_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Gap Color R", "spinbox", &((self.gap_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Gap Color G", "spinbox", &((self.gap_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(net_settings_node, "Gap Color B", "spinbox", &((self.gap_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        let net_node = find_or_create_subnet(&mut self.fs_root, "Network", "utility", (2.0, 0.0));
+        net_node.children.clear();
+
+        ensure_param(net_node, "File", "section", "", &[], None, None, None);
+        ensure_param(net_node, "New", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Open", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Save", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Save As", "button", "", &[], None, None, None);
+
+        ensure_param(net_node, "Edit", "section", "", &[], None, None, None);
+        ensure_param(net_node, "Undo", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Redo", "button", "", &[], None, None, None);
+
+        ensure_param(net_node, "View", "section", "", &[], None, None, None);
+        ensure_param(net_node, "Zoom In", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Zoom Out", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Circular Pane", "choice", if self.circular_network_pane { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(net_node, "Detach Pane", "button", "", &[], None, None, None);
+        ensure_param(net_node, "Close Pane", "button", "", &[], None, None, None);
+
+        ensure_param(net_node, "Settings", "section", "", &[], None, None, None);
+        ensure_param(net_node, "Node Color R", "spinbox", &((self.node_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(net_node, "Node Color G", "spinbox", &((self.node_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(net_node, "Node Color B", "spinbox", &((self.node_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
 
         // 3. Viewport subnet
-        let vp_node = find_or_create_subnet(&mut self.fs_root, "Viewport", (4.0, 0.0));
+        let vp_node = find_or_create_subnet(&mut self.fs_root, "Viewport", "utility", (4.0, 0.0));
+        vp_node.children.clear();
 
-        // Camera node - dynamically build options
-        let camera_node = find_or_create_subnet(vp_node, "Camera", (0.0, 0.0));
-        if let Some(p) = camera_node.params.iter_mut().find(|p| p.name == "Active Camera") {
+        ensure_param(vp_node, "Camera", "section", "", &[], None, None, None);
+        if let Some(p) = vp_node.params.iter_mut().find(|p| p.name == "Active Camera") {
             p.options = camera_options.clone();
             if !p.options.contains(&p.default) {
                 p.default = "Default Camera".to_string();
             }
         } else {
-            ensure_param(camera_node, "Active Camera", "choice", &self.active_camera, &camera_options_refs, None, None, None);
+            ensure_param(vp_node, "Active Camera", "choice", &self.active_camera, &camera_options_refs, None, None, None);
         }
 
-        let display_node = find_or_create_subnet(vp_node, "Display", (2.0, 0.0));
-        ensure_param(display_node, "Square Aspect", "choice", if self.square_viewport { "true" } else { "false" }, &["false", "true"], None, None, None);
-
-        let guides_node = find_or_create_subnet(vp_node, "Guides", (4.0, 0.0));
-        ensure_param(guides_node, "Show Grid", "choice", if self.show_grid { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(guides_node, "Cube", "choice", if self.show_cube { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(guides_node, "Origin", "choice", if self.show_origin { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(guides_node, "Camera Pivot", "choice", if self.show_camera_pivot { "true" } else { "false" }, &["false", "true"], None, None, None);
-
-        let vp_view_node = find_or_create_subnet(vp_node, "View", (6.0, 0.0));
-        ensure_param(vp_view_node, "Close Pane", "button", "", &[], None, None, None);
-
-        let vp_settings_node = find_or_create_subnet(vp_node, "Settings", (8.0, 0.0));
-        ensure_param(vp_settings_node, "Show Grid Guide", "choice", if self.show_grid { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(vp_settings_node, "Show Reference Cube", "choice", if self.show_cube { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(vp_settings_node, "Show Origin Axes", "choice", if self.show_origin { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(vp_settings_node, "Show Camera Pivot", "choice", if self.show_camera_pivot { "true" } else { "false" }, &["false", "true"], None, None, None);
-        ensure_param(vp_settings_node, "Grid Thickness", "spinbox", &((self.grid_thickness * 1000.0) as i32).to_string(), &[], Some(2.0), Some(200.0), Some(1.0));
-        ensure_param(vp_settings_node, "Origin Guide Size", "spinbox", &((self.origin_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
-        ensure_param(vp_settings_node, "Camera Pivot Size", "spinbox", &((self.camera_pivot_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
-        ensure_param(vp_settings_node, "BG Color R", "spinbox", &((self.viewport_bg_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_settings_node, "BG Color G", "spinbox", &((self.viewport_bg_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_settings_node, "BG Color B", "spinbox", &((self.viewport_bg_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_settings_node, "Grid Color R", "spinbox", &((self.grid_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_settings_node, "Grid Color G", "spinbox", &((self.grid_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_settings_node, "Grid Color B", "spinbox", &((self.grid_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "Display", "section", "", &[], None, None, None);
+        ensure_param(vp_node, "Square Aspect", "choice", if self.square_viewport { "true" } else { "false" }, &["false", "true"], None, None, None);
+
+        ensure_param(vp_node, "Guides", "section", "", &[], None, None, None);
+        ensure_param(vp_node, "Show Grid", "choice", if self.show_grid { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Cube", "choice", if self.show_cube { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Origin", "choice", if self.show_origin { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Camera Pivot", "choice", if self.show_camera_pivot { "true" } else { "false" }, &["false", "true"], None, None, None);
+
+        ensure_param(vp_node, "View", "section", "", &[], None, None, None);
+        ensure_param(vp_node, "Close Pane", "button", "", &[], None, None, None);
+
+        ensure_param(vp_node, "Settings", "section", "", &[], None, None, None);
+        ensure_param(vp_node, "Show Grid Guide", "choice", if self.show_grid { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Show Reference Cube", "choice", if self.show_cube { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Show Origin Axes", "choice", if self.show_origin { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Show Camera Pivot", "choice", if self.show_camera_pivot { "true" } else { "false" }, &["false", "true"], None, None, None);
+        ensure_param(vp_node, "Grid Thickness", "spinbox", &((self.grid_thickness * 1000.0) as i32).to_string(), &[], Some(2.0), Some(200.0), Some(1.0));
+        ensure_param(vp_node, "Origin Guide Size", "spinbox", &((self.origin_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
+        ensure_param(vp_node, "Camera Pivot Size", "spinbox", &((self.camera_pivot_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
+        ensure_param(vp_node, "BG Color R", "spinbox", &((self.viewport_bg_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "BG Color G", "spinbox", &((self.viewport_bg_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "BG Color B", "spinbox", &((self.viewport_bg_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "Grid Color R", "spinbox", &((self.grid_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "Grid Color G", "spinbox", &((self.grid_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "Grid Color B", "spinbox", &((self.grid_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
 
         // 4. Parameters subnet
-        let param_node = find_or_create_subnet(&mut self.fs_root, "Parameters", (6.0, 0.0));
+        let param_node = find_or_create_subnet(&mut self.fs_root, "Parameters", "utility", (6.0, 0.0));
+        param_node.children.clear();
 
-        let preset_node = find_or_create_subnet(param_node, "Preset", (0.0, 0.0));
-        ensure_param(preset_node, "Default", "button", "", &[], None, None, None);
-        ensure_param(preset_node, "Custom", "button", "", &[], None, None, None);
+        ensure_param(param_node, "Preset", "section", "", &[], None, None, None);
+        ensure_param(param_node, "Default", "button", "", &[], None, None, None);
+        ensure_param(param_node, "Custom", "button", "", &[], None, None, None);
 
-        let reset_node = find_or_create_subnet(param_node, "Reset", (2.0, 0.0));
-        ensure_param(reset_node, "All", "button", "", &[], None, None, None);
+        ensure_param(param_node, "Reset", "section", "", &[], None, None, None);
+        ensure_param(param_node, "All", "button", "", &[], None, None, None);
 
-        let param_view_node = find_or_create_subnet(param_node, "View", (4.0, 0.0));
-        ensure_param(param_view_node, "Close Pane", "button", "", &[], None, None, None);
+        ensure_param(param_node, "View", "section", "", &[], None, None, None);
+        ensure_param(param_node, "Close Pane", "button", "", &[], None, None, None);
 
         // 5. Spreadsheet subnet
-        let ss_node = find_or_create_subnet(&mut self.fs_root, "Spreadsheet", (8.0, 0.0));
+        let ss_node = find_or_create_subnet(&mut self.fs_root, "Spreadsheet", "utility", (8.0, 0.0));
+        ss_node.children.clear();
 
-        let ss_view_node = find_or_create_subnet(ss_node, "View", (0.0, 0.0));
-        ensure_param(ss_view_node, "Close Pane", "button", "", &[], None, None, None);
+        ensure_param(ss_node, "View", "section", "", &[], None, None, None);
+        ensure_param(ss_node, "Close Pane", "button", "", &[], None, None, None);
     }
 
     pub(crate) fn apply_settings_from_menubar_subnets(&mut self) {
         // Read Settings from Network subnet
         if let Some(net_idx) = self.fs_root.children.iter().position(|c| c.name == "Network") {
-            let subnet = &self.fs_root.children[net_idx];
-            if let Some(settings_idx) = subnet.children.iter().position(|c| c.name == "Settings") {
-                let node = &subnet.children[settings_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Snap to Grid" => if let Ok(val) = p.default.parse::<bool>() { self.grid_snap_enabled = val; }
-                        "Grid Visible" => if let Ok(val) = p.default.parse::<bool>() { self.network_grid_visible = val; }
-                        "Uniform Background" => if let Ok(val) = p.default.parse::<bool>() { self.uniform_background = val; }
-                        "Opacity" => if let Ok(val) = p.default.parse::<f32>() { self.network_opacity = val; }
-                        "Node Color R" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[0] = val / 255.0; }
-                        "Node Color G" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[1] = val / 255.0; }
-                        "Node Color B" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[2] = val / 255.0; }
-                        "Cell Color R" => if let Ok(val) = p.default.parse::<f32>() { self.cell_color[0] = val / 255.0; }
-                        "Cell Color G" => if let Ok(val) = p.default.parse::<f32>() { self.cell_color[1] = val / 255.0; }
-                        "Cell Color B" => if let Ok(val) = p.default.parse::<f32>() { self.cell_color[2] = val / 255.0; }
-                        "Gap Color R" => if let Ok(val) = p.default.parse::<f32>() { self.gap_color[0] = val / 255.0; }
-                        "Gap Color G" => if let Ok(val) = p.default.parse::<f32>() { self.gap_color[1] = val / 255.0; }
-                        "Gap Color B" => if let Ok(val) = p.default.parse::<f32>() { self.gap_color[2] = val / 255.0; }
-                        _ => {}
-                    }
-                }
-            }
-            if let Some(view_idx) = subnet.children.iter().position(|c| c.name == "View") {
-                let node = &subnet.children[view_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Circular Pane" => if let Ok(val) = p.default.parse::<bool>() { self.circular_network_pane = val; }
-                        _ => {}
-                    }
+            let node = &self.fs_root.children[net_idx];
+            for p in &node.params {
+                match p.name.as_str() {
+                    "Node Color R" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[0] = val / 255.0; }
+                    "Node Color G" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[1] = val / 255.0; }
+                    "Node Color B" => if let Ok(val) = p.default.parse::<f32>() { self.node_color[2] = val / 255.0; }
+                    "Circular Pane" => if let Ok(val) = p.default.parse::<bool>() { self.circular_network_pane = val; }
+                    _ => {}
                 }
             }
         }
 
         // Read Settings from Viewport subnet
         if let Some(vp_idx) = self.fs_root.children.iter().position(|c| c.name == "Viewport") {
-            let subnet = &self.fs_root.children[vp_idx];
-            if let Some(settings_idx) = subnet.children.iter().position(|c| c.name == "Settings") {
-                let node = &subnet.children[settings_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Show Grid Guide" => if let Ok(val) = p.default.parse::<bool>() { self.show_grid = val; }
-                        "Show Reference Cube" => if let Ok(val) = p.default.parse::<bool>() { self.show_cube = val; }
-                        "Show Origin Axes" => if let Ok(val) = p.default.parse::<bool>() { self.show_origin = val; }
-                        "Show Camera Pivot" => if let Ok(val) = p.default.parse::<bool>() { self.show_camera_pivot = val; }
-                        "Grid Thickness" => if let Ok(val) = p.default.parse::<f32>() { self.grid_thickness = val / 1000.0; }
-                        "Origin Guide Size" => if let Ok(val) = p.default.parse::<f32>() { self.origin_size = val / 10.0; }
-                        "Camera Pivot Size" => if let Ok(val) = p.default.parse::<f32>() { self.camera_pivot_size = val / 10.0; }
-                        "BG Color R" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[0] = val / 255.0; }
-                        "BG Color G" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[1] = val / 255.0; }
-                        "BG Color B" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[2] = val / 255.0; }
-                        "Grid Color R" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[0] = val / 255.0; }
-                        "Grid Color G" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[1] = val / 255.0; }
-                        "Grid Color B" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[2] = val / 255.0; }
-                        _ => {}
-                    }
-                }
-            }
-            if let Some(disp_idx) = subnet.children.iter().position(|c| c.name == "Display") {
-                let node = &subnet.children[disp_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Square Aspect" => if let Ok(val) = p.default.parse::<bool>() { self.square_viewport = val; }
-                        _ => {}
-                    }
-                }
-            }
-            if let Some(guides_idx) = subnet.children.iter().position(|c| c.name == "Guides") {
-                let node = &subnet.children[guides_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Show Grid" => if let Ok(val) = p.default.parse::<bool>() { self.show_grid = val; }
-                        "Cube" => if let Ok(val) = p.default.parse::<bool>() { self.show_cube = val; }
-                        "Origin" => if let Ok(val) = p.default.parse::<bool>() { self.show_origin = val; }
-                        "Camera Pivot" => if let Ok(val) = p.default.parse::<bool>() { self.show_camera_pivot = val; }
-                        _ => {}
-                    }
-                }
-            }
-            if let Some(camera_idx) = subnet.children.iter().position(|c| c.name == "Camera") {
-                let node = &subnet.children[camera_idx];
-                for p in &node.params {
-                    match p.name.as_str() {
-                        "Active Camera" => self.active_camera = p.default.clone(),
-                        _ => {}
-                    }
+            let node = &self.fs_root.children[vp_idx];
+            for p in &node.params {
+                match p.name.as_str() {
+                    "Show Grid Guide" => if let Ok(val) = p.default.parse::<bool>() { self.show_grid = val; }
+                    "Show Reference Cube" => if let Ok(val) = p.default.parse::<bool>() { self.show_cube = val; }
+                    "Show Origin Axes" => if let Ok(val) = p.default.parse::<bool>() { self.show_origin = val; }
+                    "Show Camera Pivot" => if let Ok(val) = p.default.parse::<bool>() { self.show_camera_pivot = val; }
+                    "Grid Thickness" => if let Ok(val) = p.default.parse::<f32>() { self.grid_thickness = val / 1000.0; }
+                    "Origin Guide Size" => if let Ok(val) = p.default.parse::<f32>() { self.origin_size = val / 10.0; }
+                    "Camera Pivot Size" => if let Ok(val) = p.default.parse::<f32>() { self.camera_pivot_size = val / 10.0; }
+                    "BG Color R" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[0] = val / 255.0; }
+                    "BG Color G" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[1] = val / 255.0; }
+                    "BG Color B" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[2] = val / 255.0; }
+                    "Grid Color R" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[0] = val / 255.0; }
+                    "Grid Color G" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[1] = val / 255.0; }
+                    "Grid Color B" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[2] = val / 255.0; }
+                    "Square Aspect" => if let Ok(val) = p.default.parse::<bool>() { self.square_viewport = val; }
+                    "Show Grid" => if let Ok(val) = p.default.parse::<bool>() { self.show_grid = val; }
+                    "Cube" => if let Ok(val) = p.default.parse::<bool>() { self.show_cube = val; }
+                    "Origin" => if let Ok(val) = p.default.parse::<bool>() { self.show_origin = val; }
+                    "Camera Pivot" => if let Ok(val) = p.default.parse::<bool>() { self.show_camera_pivot = val; }
+                    "Active Camera" => self.active_camera = p.default.clone(),
+                    _ => {}
                 }
             }
         }
diff --git a/src/render.rs b/src/render.rs
index 4bc57df..c756c2f 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -63,9 +63,7 @@ impl State {
         let sh = self.height;
 
         let node_area_y = self.positions[CONTENT_IDX].1;
-        let dialog_open = self.node_palette_visible;
-        let show_cursor = self.drag_widget.is_none()
-            && !dialog_open;
+        let show_cursor = self.drag_widget.is_none();
 
         let clip = if self.circular_network_pane {
             (
@@ -103,7 +101,6 @@ impl State {
             };
             (self.has_any_open_menu(i), base_key)
         });
-        println!("DEBUG_DRAW_ORDER: {:?}", draw_order.iter().map(|&i| (i, self.widgets[i].visible(), self.positions[i])).collect::<Vec<_>>());
 
         let mut visited = vec![false; self.widgets.len()];
         for &i in &draw_order {
@@ -266,9 +263,27 @@ impl State {
         }
 
         // Dropdown popover
-        if w.visible() && self.focused_widget == Some(idx) {
-            if let Some((px, py, pw, ph)) = w.popover_rect() {
-                push_widget_popover_vertices(px, py, pw, ph, sw, sh, verts);
+        if w.visible() && (self.focused_widget == Some(idx) || idx == PARAM_IDX) {
+            let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+            w.render_popover(&mut popover_pc);
+            for (color, px, py, pw, ph) in popover_pc.rects {
+                let ndc_x = (px / sw) * 2.0 - 1.0;
+                let ndc_y = 1.0 - (py / sh) * 2.0;
+                let ndc_w = (pw / sw) * 2.0;
+                let ndc_h = (ph / sh) * 2.0;
+
+                let v_tl = Vertex { position: [ndc_x, ndc_y], color, clip_circle: [0.0, 0.0, 0.0] };
+                let v_tr = Vertex { position: [ndc_x + ndc_w, ndc_y], color, clip_circle: [0.0, 0.0, 0.0] };
+                let v_bl = Vertex { position: [ndc_x, ndc_y - ndc_h], color, clip_circle: [0.0, 0.0, 0.0] };
+                let v_br = Vertex { position: [ndc_x + ndc_w, ndc_y - ndc_h], color, clip_circle: [0.0, 0.0, 0.0] };
+
+                verts.push(v_tl);
+                verts.push(v_tr);
+                verts.push(v_bl);
+
+                verts.push(v_tr);
+                verts.push(v_br);
+                verts.push(v_bl);
             }
         }
     }
@@ -299,6 +314,7 @@ impl State {
     }
 
     pub(crate) fn upload_vertices(&mut self) {
+        self.text_dirty = true;
         let mut verts = std::mem::take(&mut self.vertex_data);
         self.collect_vertices(&mut verts);
         self.vertex_count = verts.len() as u32;
@@ -317,31 +333,24 @@ impl State {
     }
 
     pub(crate) fn rebuild_scene_geometry(&mut self) {
-        let mut geom = crate::geometry::network_sphere_vertices(&self.fs_root);
+        let mut ocl_error = None;
+        let geom = network_sphere_vertices_with_errors(&self.fs_root, &mut ocl_error);
 
-        fn collect_opencl_codes(node: &FsNode, codes: &mut Vec<String>) {
+        fn has_visible_opencl(node: &FsNode) -> bool {
             if node.node_type.eq_ignore_ascii_case("opencl") && node.geometry_visible {
-                let code = node.params.iter()
-                    .find(|p| p.name.eq_ignore_ascii_case("code"))
-                    .map(|p| p.default.clone())
-                    .unwrap_or_else(String::new);
-                codes.push(code);
+                return true;
             }
             for child in &node.children {
-                collect_opencl_codes(child, codes);
-            }
-        }
-        let mut opencl_codes = Vec::new();
-        collect_opencl_codes(&self.fs_root, &mut opencl_codes);
-
-        for code in &opencl_codes {
-            if !code.is_empty() {
-                if let Err(e) = crate::geometry::run_opencl_kernel(code, &mut geom) {
-                    self.update_status_text(&format!("OpenCL Error: {}", e));
+                if has_visible_opencl(child) {
+                    return true;
                 }
             }
+            false
         }
-        if !opencl_codes.is_empty() {
+
+        if let Some(e) = ocl_error {
+            self.update_status_text(&format!("OpenCL Error: {}", e));
+        } else if has_visible_opencl(&self.fs_root) {
             self.update_status_text("OpenCL kernel executed successfully.");
         } else {
             self.update_status_text("Geometry updated successfully.");
@@ -360,13 +369,54 @@ impl State {
             });
         }
         self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_spheres, 0, data);
+        self.viewport_dirty = true;
     }
 
     pub(crate) fn update_status_text(&mut self, text: &str) {
-        self.widgets[STATUS_IDX].set_text(text);
+        if self.last_status_text != text {
+            self.last_status_text = text.to_string();
+            self.widgets[STATUS_IDX].set_text(text);
+            self.text_dirty = true;
+        }
     }
 
     pub(crate) fn prepare_text(&mut self) {
+        let mut current_popovers = Vec::new();
+        {
+            fn collect_popovers(
+                w: &dyn Element,
+                popovers: &mut Vec<(f32, f32, f32, f32)>,
+                ctx: &clear_ui::context::UiContext,
+            ) {
+                if let Some(rect) = w.popover_rect() {
+                    popovers.push(rect);
+                }
+                for child_ptr in w.children(ctx) {
+                    unsafe {
+                        if let Some(child) = child_ptr.as_ref() {
+                            collect_popovers(child, popovers, ctx);
+                        }
+                    }
+                }
+            }
+
+            for w in &self.widgets {
+                if w.visible() {
+                    collect_popovers(w.as_ref(), &mut current_popovers, &self.ui_context);
+                }
+            }
+        }
+
+        if current_popovers != self.last_popover_rects {
+            self.last_popover_rects = current_popovers;
+            self.text_dirty = true;
+        }
+
+        if !self.text_dirty {
+            return;
+        }
+        self.text_dirty = false;
+
         // 1. Prepare text on all widgets using self.wgpu_adapter.font_system
         for (i, w) in self.widgets.iter_mut().enumerate() {
             let is_menubar = i == HEADER_IDX || i == LEFT_MENUBAR_IDX || i == RIGHT_MENUBAR_IDX || i == PARAM_MENUBAR_IDX || i == SPREADSHEET_MENUBAR_IDX;
@@ -457,10 +507,56 @@ impl State {
             }
         }
 
+        // Pass 1b: Populate text_buffer_cache with popover texts
+        for (i, w) in widgets.iter().enumerate() {
+            if !w.visible() {
+                continue;
+            }
+            let is_menubar = i == HEADER_IDX || i == LEFT_MENUBAR_IDX || i == RIGHT_MENUBAR_IDX || i == PARAM_MENUBAR_IDX || i == SPREADSHEET_MENUBAR_IDX;
+            if is_menubar {
+                continue;
+            }
+            if self.focused_widget == Some(i) || i == PARAM_IDX {
+                let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+                w.render_popover(&mut popover_pc);
+                for (t, size, _x, _y, _tc, font_opt, _bounds) in popover_pc.texts {
+                    let key = (t.clone(), (size * 100.0) as u32, font_opt.clone());
+                    if !text_buffer_cache.contains_key(&key) {
+                        let buf = make_text_buffer_with_font(font_system, &t, size, font_opt.as_deref());
+                        text_buffer_cache.insert(key, buf);
+                    }
+                }
+            }
+        }
+
         let viewport = Resolution { width: *physical_width, height: *physical_height };
         text_viewport.update(queue, viewport);
         let s = *scale as f32;
 
+        let mut popovers = Vec::new();
+        fn collect_popovers(
+            w: &dyn Element,
+            popovers: &mut Vec<(f32, f32, f32, f32)>,
+            ctx: &clear_ui::context::UiContext,
+        ) {
+            if let Some(rect) = w.popover_rect() {
+                popovers.push(rect);
+            }
+            for child_ptr in w.children(ctx) {
+                unsafe {
+                    if let Some(child) = child_ptr.as_ref() {
+                        collect_popovers(child, popovers, ctx);
+                    }
+                }
+            }
+        }
+
+        for w in widgets {
+            if w.visible() {
+                collect_popovers(w.as_ref(), &mut popovers, ui_context);
+            }
+        }
+
         let mut areas: Vec<TextArea> = Vec::new();
 
         // Temporary storage for legacy buffers generated during this frame
@@ -548,6 +644,21 @@ impl State {
                             continue;
                         }
                     }
+                    // Overlap check
+                    let mut overlaps = false;
+                    let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0) / s;
+                    let th = (buf.layout_runs().count() as f32 * 14.0).max(14.0);
+                    for &(px, py, pw, ph) in &popovers {
+                        let x_overlap = x <= px + pw && (x + tw) >= px;
+                        let y_overlap = y <= py + ph && (y + th) >= py;
+                        if x_overlap && y_overlap {
+                            overlaps = true;
+                            break;
+                        }
+                    }
+                    if overlaps {
+                        continue;
+                    }
                     areas.push(TextArea {
                         buffer: buf,
                         left: (x * s).round(),
@@ -598,6 +709,26 @@ impl State {
                         }
                         let key = (label.text.clone(), (label.font_size * 100.0) as u32, font_opt.clone());
                         let buf_ref = text_buffer_cache.get(&key).unwrap();
+
+                        // Overlap check
+                        let mut overlaps = false;
+                        let tw = buf_ref.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0) / s;
+                        let th = label.font_size * 1.4;
+                        for &(px, py, pw, ph) in &popovers {
+                            let x_overlap = label.x <= px + pw && (label.x + tw) >= px;
+                            let y_overlap = label.y <= py + ph && (label.y + th) >= py;
+                            if x_overlap && y_overlap {
+                                overlaps = true;
+                                break;
+                            }
+                        }
+                        if label.text.contains("Save") {
+                            println!("DEBUG SAVE OVERLAP: i={}, label_text={:?}, label.x={}, label.y={}, tw={}, th={}, popovers={:?}, overlaps={}", i, label.text, label.x, label.y, tw, th, popovers, overlaps);
+                        }
+                        if overlaps {
+                            continue;
+                        }
+
                         legacy_buffers.push(buf_ref);
                         legacy_labels.push(label);
                         legacy_bounds.push(item_bounds);
@@ -619,6 +750,65 @@ impl State {
             });
         }
 
+        // Add popover text areas
+        for (i, w) in widgets.iter().enumerate() {
+            if !w.visible() {
+                continue;
+            }
+            let is_menubar = i == HEADER_IDX || i == LEFT_MENUBAR_IDX || i == RIGHT_MENUBAR_IDX || i == PARAM_MENUBAR_IDX || i == SPREADSHEET_MENUBAR_IDX;
+            if is_menubar {
+                continue;
+            }
+            if self.focused_widget == Some(i) || i == PARAM_IDX {
+                let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+                w.render_popover(&mut popover_pc);
+                for (t, size, x, y, tc, font_opt, label_bounds) in popover_pc.texts {
+                    let key = (t.clone(), (size * 100.0) as u32, font_opt.clone());
+                    if let Some(buf_ref) = text_buffer_cache.get(&key) {
+                        let mut item_bounds = TextBounds {
+                            left: 0,
+                            top: 0,
+                            right: *physical_width as i32,
+                            bottom: *physical_height as i32,
+                        };
+                        if let Some([l, t_bound, r, b]) = label_bounds {
+                            let pl = (l * s).round() as i32;
+                            let pt = (t_bound * s).round() as i32;
+                            let pr = (r * s).round() as i32;
+                            let pb = (b * s).round() as i32;
+                            item_bounds = TextBounds {
+                                left: item_bounds.left.max(pl),
+                                top: item_bounds.top.max(pt),
+                                right: item_bounds.right.min(pr),
+                                bottom: item_bounds.bottom.min(pb),
+                            };
+                        }
+                        areas.push(TextArea {
+                            buffer: buf_ref,
+                            left: (x * s).round(),
+                            top: (y * s).round(),
+                            scale: s,
+                            bounds: item_bounds,
+                            default_color: glyphon::Color::rgb(
+                                (tc[0] * 255.0) as u8,
+                                (tc[1] * 255.0) as u8,
+                                (tc[2] * 255.0) as u8,
+                            ),
+                            custom_glyphs: &[],
+                        });
+                    }
+                }
+            }
+        }
+
+        if !popovers.is_empty() {
+            for (idx, area) in areas.iter().enumerate() {
+                for run in area.buffer.layout_runs() {
+                    println!("DEBUG: Area {}, text={:?}, x={}, y={}", idx, run.text, area.left, area.top);
+                }
+            }
+        }
+
         text_renderer.prepare(device, queue, font_system, text_atlas, text_viewport, areas, swash_cache).unwrap();
 
         // Process curved labels
diff --git a/src/window.rs b/src/window.rs
index 6d57818..f9dd0e9 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -659,6 +659,9 @@ impl AppState {
             xkeysym::Keysym::k | xkeysym::Keysym::K => Key::Character("k".into()),
             xkeysym::Keysym::l | xkeysym::Keysym::L => Key::Character("l".into()),
             xkeysym::Keysym::s | xkeysym::Keysym::S => Key::Character("s".into()),
+            xkeysym::Keysym::c | xkeysym::Keysym::C => Key::Character("c".into()),
+            xkeysym::Keysym::x | xkeysym::Keysym::X => Key::Character("x".into()),
+            xkeysym::Keysym::v | xkeysym::Keysym::V => Key::Character("v".into()),
             xkeysym::Keysym::grave => Key::Character("`".into()),
             _ => {
                 if let Some(ref text) = event.utf8 {
@@ -726,12 +729,28 @@ impl AppState {
     pub fn process_event(&mut self, ev: WindowEvent) {
         if let Some(state) = &mut self.state {
             let mut changed = state.handle_event(&ev);
-            state.sync_settings_from_paginator();
 
             if let Some(seg) = state.path_mut().path_click() {
                 if seg < state.current_path.len() {
+                    let exited_idx = state.current_path.get(seg).copied();
                     state.current_path.truncate(seg);
                     state.on_path_changed();
+                    if let Some(idx) = exited_idx {
+                        let pos = {
+                            let dir = state.current_dir();
+                            if idx < dir.children.len() {
+                                Some(dir.children[idx].position)
+                            } else {
+                                None
+                            }
+                        };
+                        if let Some((pos_x, pos_y)) = pos {
+                            state.grid_cursor_col = pos_x as i32;
+                            state.grid_cursor_row = pos_y as i32;
+                            state.sync_cursor_and_selection();
+                            state.upload_vertices();
+                        }
+                    }
                     changed = true;
                 }
             }
@@ -1309,9 +1328,7 @@ impl AppState {
                 CustomEvent::PostAction(action, tx) => {
                     let res = match action {
                         HttpAction::Up => {
-                            if !state.current_path.is_empty() {
-                                state.current_path.pop();
-                                state.on_path_changed();
+                            if state.move_up() {
                                 needs_redraw = true;
                                 Ok("Moved up".to_string())
                             } else {
@@ -1320,7 +1337,7 @@ impl AppState {
                         }
                         HttpAction::Enter { slot } => {
                             let dir = state.current_dir();
-                            if slot < dir.children.len() && (dir.children[slot].node_type == "node" || dir.children[slot].node_type == "opencl" || !dir.children[slot].children.is_empty()) {
+                            if slot < dir.children.len() && (dir.children[slot].node_type == "node" || dir.children[slot].node_type == "utility" || !dir.children[slot].children.is_empty()) {
                                 state.current_path.push(slot);
                                 state.on_path_changed();
                                 needs_redraw = true;
@@ -1385,12 +1402,16 @@ impl AppState {
                         HttpAction::ToggleGeometry { slot } => {
                             let active_nodes = state.current_dir().children.len();
                             if slot < active_nodes {
-                                let visible = !state.current_dir().children[slot].geometry_visible;
-                                state.current_dir_mut().children[slot].geometry_visible = visible;
-                                state.sync_nodes();
-                                state.rebuild_scene_geometry();
-                                needs_redraw = true;
-                                Ok(format!("Geometry visible: {}", visible))
+                                if state.current_dir().children[slot].node_type == "utility" {
+                                    Err("Cannot toggle geometry visibility on utility nodes".to_string())
+                                } else {
+                                    let visible = !state.current_dir().children[slot].geometry_visible;
+                                    state.current_dir_mut().children[slot].geometry_visible = visible;
+                                    state.sync_nodes();
+                                    state.rebuild_scene_geometry();
+                                    needs_redraw = true;
+                                    Ok(format!("Geometry visible: {}", visible))
+                                }
                             } else {
                                 Err("Slot out of bounds".to_string())
                             }
@@ -1402,43 +1423,39 @@ impl AppState {
                             });
                             if let Some(idx) = template_idx {
                                 let mut node = state.node_templates[idx].node.clone();
-                                let (nx, ny) = state.find_empty_cell(x, y, None);
-                                node.position = (nx, ny);
-                                if let Some(n) = name {
-                                    node.name = n;
+                                let mut allowed = true;
+                                let is_in_utility = !state.current_path.is_empty() && state.fs_root.children[state.current_path[0]].node_type == "utility";
+                                if is_in_utility {
+                                    if crate::geometry::is_geometry_node_type(&node.node_type) {
+                                        allowed = false;
+                                    }
+                                }
+                                if !allowed {
+                                    Err("Utility nodes cannot contain geometry.".to_string())
+                                } else {
+                                    let (nx, ny) = state.find_empty_cell(x, y, None);
+                                    node.position = (nx, ny);
+                                    if let Some(n) = name {
+                                        node.name = n;
+                                    } else {
+                                        node.name = state.get_lowest_unused_name(&node.name);
+                                    }
+                                    state.current_dir_mut().children.push(node);
+                                    state.sync_nodes();
+                                    state.rebuild_positions();
+                                    state.apply_layout();
+                                    state.update_panel_bounds();
+                                    state.rebuild_scene_geometry();
+                                    state.upload_vertices();
+                                    needs_redraw = true;
+                                    Ok("Node added".to_string())
                                 }
-                                state.current_dir_mut().children.push(node);
-                                state.sync_nodes();
-                                state.rebuild_positions();
-                                state.apply_layout();
-                                state.update_panel_bounds();
-                                state.upload_vertices();
-                                needs_redraw = true;
-                                Ok("Node added".to_string())
                             } else {
                                 Err(format!("Template '{}' not found", template_name))
                             }
                         }
                         HttpAction::DeleteNode { slot } => {
-                            let len = state.current_dir().children.len();
-                            if slot < len {
-                                state.current_dir_mut().children.remove(slot);
-                                if let Some(focused) = state.focused_widget {
-                                    if focused == CONTENT_IDX {
-                                        if let Some(sel_idx) = state.graph().selected_node() {
-                                            if sel_idx == slot {
-                                                state.graph_mut().set_selected_node(None);
-                                            } else if sel_idx > slot {
-                                                state.graph_mut().set_selected_node(Some(sel_idx - 1));
-                                            }
-                                        }
-                                    }
-                                }
-                                state.sync_nodes();
-                                state.rebuild_positions();
-                                state.apply_layout();
-                                state.update_panel_bounds();
-                                state.upload_vertices();
+                            if state.delete_node(slot) {
                                 needs_redraw = true;
                                 Ok("Node deleted".to_string())
                             } else {