git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit69d83c2fcf303900c67f6d9a28df7ccc4f15515a
parentd57ec5e6fd
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-28 14:27
refactor: retire relief.depth — light is the one spelling of the strength

`depth` was every config's name for the relief's light strength until
this afternoon's rename made `light` the spelling and `depth` an alias
that lost to it. An alias that keeps working is a second spelling
forever, so the loader no longer reads it: the flatten arm goes, the
`depth`/`light` pair leaves `prefer_relief_spellings` (a retired key
never reaches a registry key, so there is nothing to prefer), a
material's `finish depth=` alias goes the same way, and a config still
carrying either is reported by path with the other retired surface keys.
cce-relief reads `depth` as a seed only — a file saved before the rename
opens on its own strength — and its Save writes `light` and removes
`depth`, as before. The `window_manager.bevel_depth` compat spelling is
the compositor's old block, a different family, and stays.

Fixtures and the KDL writer tests move onto `light`; the retired-keys
test expects both `depth` spellings; the precedence test asserts that a
lone `depth` flattens to no registry key at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

 CLAUDE.md             | 11 ++++++-----
 src/bin/cce-relief.rs |  3 +++
 src/color.rs          | 33 +++++++++++++++++++++++----------
 src/config.rs         | 14 +++++++-------
 src/layout.rs         | 51 +++++++++++++++++++++++++--------------------------
 src/scene/material.rs |  6 +++---
 6 files changed, 67 insertions(+), 51 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index daf069a..83e0934 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -566,11 +566,11 @@ Spellings that are NOT current, and what the loader does with each:
 
 | Spelling | Status |
 |---|---|
-| `relief.depth`, `window_manager.bevel_depth`, a material's `finish depth=` | alias of `relief.light` / `finish light=`; `light` wins when both are present |
+| `window_manager.bevel_depth` / `.bevel_width` | compat aliases of `relief.light` / `.width` — the compositor's old block, a different family |
 | `relief.height` / `.profile`, `relief.edge_height` / `.edge_profile` | aliases of `wall.*` / `edge.*`; the node wins when both are present |
 | `window_manager.bevel_width` | alias of `relief.width` |
 | `param.color` (+ top-level `plate_opacity`) | alias of `plate.pane.color`, multiplied by the opacity line |
-| `plate.blur` / `.radius` / `.backdrop_compression` / `.refraction`, `frost.backdrop_compression`, `plate.bevel_width` | RETIRED: reported by path (`color::retired_surface_keys`), not read |
+| `plate.blur` / `.radius` / `.backdrop_compression` / `.refraction`, `frost.backdrop_compression`, `plate.bevel_width`, `relief.depth`, a material's `finish depth=` | RETIRED: reported by path (`color::retired_surface_keys`), not read; cce-relief seeds from `depth` once and writes `light` |
 | `relief.wall.knobs` / `edge.knobs`, `profile_knobs` / `edge_knobs` | not style: cce-relief's own state (`~/.config/cce/cce-relief/state.kdl`); read once as a seed, removed on its next Save |
 
 Where each rule is argued, by its lead-in: **Frost is one block** and
@@ -756,9 +756,10 @@ relief light=0.15 width=9.3 {
 `width` (the run of both, one number — see the roll-width note below) and
 `light` (how hard the light falls across either shape — NOT a length, it is
 `bevel_depth` → `Finish.strength`; `depth`, what every config said until
-2026-09-28, is its alias, losing to `light` when a file carries both, and
-cce-relief's Save writes `light` and takes `depth` off) stay on the node
-itself, since both shapes share them. `height` is a length — the wall's drop, the edge's rise —
+2026-09-28, was its alias for the rest of that day and is retired — reported
+by path, not read, seeded from once by cce-relief whose Save writes `light`
+and takes `depth` off) stay on the node itself, since both shapes share
+them. `height` is a length — the wall's drop, the edge's rise —
 and `height=(mm)0.3` is honest geometry resolved through the metric; unset, a
 carve drops `relief_shade::RECESS_DEPTH` (0.6) of its wall (saturating at the
 DE roll width) and the roll is a quarter-round of radius width, the look every
diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 7bb39a2..043c4b5 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -1471,6 +1471,9 @@ impl Application for BevelPopup {
             saved_edge.or_else(|| rel_shape_str("edge", "knobs", "edge_knobs").as_deref().and_then(parse_knobs)),
         );
 
+        // `depth` is retired (the loader does not read it); read HERE as a
+        // seed only, so a file saved before the rename opens on its own
+        // light strength and Save writes it back as `light`.
         let depth = key_spec
             .as_ref()
             .and_then(|s| s.light)
diff --git a/src/color.rs b/src/color.rs
index 7e4aea2..43cf279 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -683,7 +683,7 @@ fn parse_and_set_colors(content: &str) {
     let retired = retired_surface_keys(&val);
     if !retired.is_empty() {
         log::warn!(
-            "retired style.surface keys in config: {} — the frost is `plate {{ frost radius= compression= refraction= }}` (a material's `frost` child spells `compression`), and every roll's width is `relief width=`",
+            "retired style.surface keys in config: {} — the frost is `plate {{ frost radius= compression= refraction= }}` (a material's `frost` child spells `compression`), every roll's width is `relief width=`, and the light strength is `relief light=` (a material's `finish light=`)",
             retired.join(", ")
         );
     }
@@ -748,7 +748,7 @@ fn parse_and_set_colors(content: &str) {
                     MaterialDef {
                         tint: node.get("color").and_then(|v| v.as_str()).and_then(parse_hex),
                         frost,
-                        light: fk("light").or(fk("depth")),
+                        light: fk("light"),
                         spec: fk("spec"),
                         shininess: fk("shininess"),
                         curvature: fk("curvature"),
@@ -771,10 +771,12 @@ fn parse_and_set_colors(content: &str) {
 /// the dotted paths a user would grep for: the four flat frost keys under
 /// `plate` (`blur`, `radius`, `backdrop_compression`, `refraction`),
 /// `backdrop_compression` inside any `frost` child (the plate's, or a named
-/// material's), whose one name is `compression`, and `plate.bevel_width`,
-/// the pane roll's former width of its own (an override for the rest of
-/// 2026-09-28, retired that evening: every roll is `relief.width`). Empty
-/// for a clean config.
+/// material's), whose one name is `compression`; `plate.bevel_width`, the
+/// pane roll's former width of its own (every roll is `relief.width`); and
+/// `relief.depth` with `depth` inside any material's `finish` child, the
+/// light strength's former name (`light`, since it is not a length). Each
+/// was an alias for part of 2026-09-28 and is not read now. Empty for a
+/// clean config.
 pub fn retired_surface_keys(val: &serde_json::Value) -> Vec<String> {
     let mut found = Vec::new();
     for k in ["blur", "radius", "backdrop_compression", "refraction", "bevel_width"] {
@@ -785,11 +787,17 @@ pub fn retired_surface_keys(val: &serde_json::Value) -> Vec<String> {
     if val.pointer("/style/surface/plate/frost/backdrop_compression").is_some() {
         found.push("style.surface.plate.frost.backdrop_compression".to_string());
     }
+    if val.pointer("/style/surface/relief/depth").is_some() {
+        found.push("style.surface.relief.depth".to_string());
+    }
     if let Some(mats) = val.pointer("/style/surface/material").and_then(|v| v.as_object()) {
         for (name, node) in mats {
             if node.pointer("/frost/backdrop_compression").is_some() {
                 found.push(format!("style.surface.material.{name}.frost.backdrop_compression"));
             }
+            if node.pointer("/finish/depth").is_some() {
+                found.push(format!("style.surface.material.{name}.finish.depth"));
+            }
         }
     }
     found
@@ -2237,19 +2245,22 @@ mod tests {
     }
 
     /// The four flat frost keys, the `backdrop_compression` spelling inside
-    /// a `frost` child and `plate.bevel_width` are reported by path; the
-    /// block itself and a material's `compression` are not.
+    /// a `frost` child, `plate.bevel_width` and `depth` (on the relief, or
+    /// in a material's `finish`) are reported by path; the block itself and
+    /// a material's `compression` / `light` are not.
     #[test]
     fn retired_surface_keys_are_named_by_path_and_the_block_is_not() {
         let clean: serde_json::Value = serde_json::json!({ "style": { "surface": {
             "plate": { "frost": { "radius": 5.5, "compression": 0.0, "refraction": 0.0 }, "color": "#6c6c7bf2" },
-            "material": { "glass": { "frost": { "compression": 0.6 } } }
+            "relief": { "light": 0.15 },
+            "material": { "glass": { "frost": { "compression": 0.6 }, "finish": { "light": 0.2 } } }
         } } });
         assert!(retired_surface_keys(&clean).is_empty());
         let old: serde_json::Value = serde_json::json!({ "style": { "surface": {
             "plate": { "blur": true, "radius": 1.5, "backdrop_compression": 0.85, "refraction": 0.3,
                        "bevel_width": 12.0, "frost": { "backdrop_compression": 0.2 } },
-            "material": { "glass": { "frost": { "backdrop_compression": 0.6 } } }
+            "relief": { "depth": 0.15 },
+            "material": { "glass": { "frost": { "backdrop_compression": 0.6 }, "finish": { "depth": 0.2 } } }
         } } });
         assert_eq!(retired_surface_keys(&old), vec![
             "style.surface.plate.blur",
@@ -2258,7 +2269,9 @@ mod tests {
             "style.surface.plate.refraction",
             "style.surface.plate.bevel_width",
             "style.surface.plate.frost.backdrop_compression",
+            "style.surface.relief.depth",
             "style.surface.material.glass.frost.backdrop_compression",
+            "style.surface.material.glass.finish.depth",
         ]);
         assert!(retired_surface_keys(&serde_json::json!({})).is_empty());
     }
diff --git a/src/config.rs b/src/config.rs
index 68d0144..87afdb5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -999,7 +999,7 @@ mod tests {
     /// which is how cce-relief's Save migrates a file in place.
     #[test]
     fn relief_wall_and_edge_keys_write_as_child_nodes_and_the_flat_ones_come_off() {
-        let content = "style {\n    surface {\n        relief depth=(f64)0.15 width=(f64)9.3 profile=\"old\" edge_knobs=(bevel)\"0.500,0.500,0.500\"\n    }\n}\n";
+        let content = "style {\n    surface {\n        relief light=(f64)0.15 width=(f64)9.3 profile=\"old\" edge_knobs=(bevel)\"0.500,0.500,0.500\"\n    }\n}\n";
         let mut doc = content.parse::<kdl::KdlDocument>().unwrap();
         let spec = "smooth;0.000:0.500,0.400:1.000,1.000:0.000";
         assert!(update_kdl_in_memory(&mut doc, "style.surface.relief.wall.profile", spec, "style"));
@@ -1014,13 +1014,13 @@ mod tests {
         assert!(!remove_kdl_in_memory(&mut doc, "style.surface.nothing.here"), "missing node: nothing to remove");
         let out = doc.to_string();
         assert_eq!(out.matches("relief").count(), 1, "one relief node: {out}");
-        assert!(out.contains("depth=(f64)0.15"), "the node keeps its properties: {out}");
+        assert!(out.contains("light=(f64)0.15"), "the node keeps its properties: {out}");
         assert!(!out.contains("profile=\"old\""), "flat profile migrated: {out}");
         assert!(!out.contains("edge_knobs"), "flat knobs migrated: {out}");
 
         let val = parse_kdl_to_json(&out);
         let relief = val.pointer("/style/surface/relief").unwrap();
-        assert_eq!(relief.get("depth").and_then(|v| v.as_f64()), Some(0.15));
+        assert_eq!(relief.get("light").and_then(|v| v.as_f64()), Some(0.15));
         assert_eq!(relief.pointer("/wall/profile").and_then(|v| v.as_str()), Some(spec));
         assert_eq!(relief.pointer("/wall/height").and_then(|v| v.as_str()), Some("0.3mm"), "{relief}");
         assert_eq!(relief.pointer("/edge/profile").and_then(|v| v.as_str()), Some(spec));
@@ -1037,12 +1037,12 @@ mod tests {
     fn relief_keys_write_as_properties_and_round_trip() {
         // `relief` is a PROP_NODES member: style.surface.relief.* must land as
         // properties on the existing relief node (the config.kdl shape), not
-        // as duplicate child nodes shadowing the depth=/width= properties.
-        let content = "style {\n    surface {\n        relief depth=(f64)0.15 width=(f64)9.3\n    }\n}\n";
+        // as duplicate child nodes shadowing the light=/width= properties.
+        let content = "style {\n    surface {\n        relief light=(f64)0.15 width=(f64)9.3\n    }\n}\n";
         let mut doc = content.parse::<kdl::KdlDocument>().unwrap();
         let spec = "smooth;0.000:0.500,0.400:1.000,1.000:0.000";
         assert!(update_kdl_in_memory(&mut doc, "style.surface.relief.profile", spec, "style"));
-        assert!(update_kdl_in_memory(&mut doc, "style.surface.relief.depth", "0.3", "style"));
+        assert!(update_kdl_in_memory(&mut doc, "style.surface.relief.light", "0.3", "style"));
         let out = doc.to_string();
         // Still one relief node, no child block grown under it.
         assert_eq!(out.matches("relief").count(), 1, "out: {out}");
@@ -1053,7 +1053,7 @@ mod tests {
         let val = parse_kdl_to_json(&out);
         let relief = val.get("style").unwrap().get("surface").unwrap().get("relief").unwrap();
         assert_eq!(relief.get("profile").unwrap().as_str().unwrap(), spec);
-        assert_eq!(relief.get("depth").unwrap().as_f64().unwrap(), 0.3);
+        assert_eq!(relief.get("light").unwrap().as_f64().unwrap(), 0.3);
         assert_eq!(relief.get("width").unwrap().as_f64().unwrap(), 9.3);
     }
 
diff --git a/src/layout.rs b/src/layout.rs
index 35d46c8..59ef75b 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -245,9 +245,12 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 // spelling survives only as a compat alias).
                 // `light` is the spelling (since 2026-09-28): it is the
                 // light strength, not a length. `depth` — what every config
-                // said until then — is the alias, and `prefer_relief_spellings`
-                // lets `light` win when a file carries both.
-                "style.surface.relief.light" | "style.surface.relief.depth" | "window_manager.bevel_depth" => "bevel_depth",
+                // said until then — was its alias for the rest of that day
+                // and is RETIRED: not read, reported by path
+                // (`color::retired_surface_keys`), removed by cce-relief's
+                // Save. The `window_manager.bevel_depth` compat spelling is
+                // a different family (the compositor's old block) and stays.
+                "style.surface.relief.light" | "window_manager.bevel_depth" => "bevel_depth",
                 "style.surface.relief.width" | "window_manager.bevel_width" => "bevel_width",
                 // The two SHAPES of the relief, each a node under it
                 // (2026-09-28): `wall` is a carve's wall — a recess, boss,
@@ -396,15 +399,15 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
 }
 
 /// The relief's current spellings win over its legacy ones: the `wall` /
-/// `edge` node spellings over the flat geometry keys, and `light` over
-/// `depth`. Each pair flattens to ONE registry key (`relief.wall.height`
-/// and `relief.height` are both `bevel_height`; `light` and `depth` are
-/// both `bevel_depth`), and `reload_config` loads the flat lines in the
-/// order the JSON hands them out, so a config carrying both spellings — a
-/// file cce-relief has saved once under the new names while an older line
+/// `edge` node spellings over the flat geometry keys. Each pair flattens to
+/// ONE registry key (`relief.wall.height` and `relief.height` are both
+/// `bevel_height`), and `reload_config` loads the flat lines in the order
+/// the JSON hands them out, so a config carrying both spellings — a file
+/// cce-relief has saved once under the new names while an older line
 /// survives, or a per-app override written in the other spelling — would
 /// otherwise be decided by which line came first. Run on the parsed config
-/// before it is flattened.
+/// before it is flattened. (`depth` versus `light` was a pair here for the
+/// rest of 2026-09-28; `depth` is retired now and never flattens at all.)
 pub(crate) fn prefer_relief_spellings(val: &mut serde_json::Value) {
     const NODE_PAIRS: &[(&str, &str, &str)] = &[
         ("height", "wall", "height"),
@@ -412,7 +415,6 @@ pub(crate) fn prefer_relief_spellings(val: &mut serde_json::Value) {
         ("edge_height", "edge", "height"),
         ("edge_profile", "edge", "profile"),
     ];
-    const FLAT_PAIRS: &[(&str, &str)] = &[("depth", "light")];
     let Some(relief) = val.pointer_mut("/style/surface/relief").and_then(|v| v.as_object_mut()) else {
         return;
     };
@@ -421,11 +423,6 @@ pub(crate) fn prefer_relief_spellings(val: &mut serde_json::Value) {
             relief.remove(*flat);
         }
     }
-    for (legacy, current) in FLAT_PAIRS {
-        if relief.get(*current).is_some() {
-            relief.remove(*legacy);
-        }
-    }
 }
 
 fn read_config() -> Option<String> {
@@ -7005,20 +7002,22 @@ mod tests {
     }
 
     /// A config carrying both spellings of one relief key is decided by the
-    /// current spelling — the node one for the geometry, `light` over
-    /// `depth` — whichever line the file wrote first: the flat pass loads
-    /// in JSON order, and `prefer_relief_spellings` is what makes the order
-    /// irrelevant. A key with no current spelling beside it is left alone.
+    /// current spelling — the node one for the geometry — whichever line
+    /// the file wrote first: the flat pass loads in JSON order, and
+    /// `prefer_relief_spellings` is what makes the order irrelevant. A key
+    /// with no current spelling beside it is left alone. The retired
+    /// `depth` is not a pair any more: it never reaches a registry key.
     #[test]
     fn the_current_spelling_of_a_relief_key_wins_over_the_legacy_one() {
-        let mut both: serde_json::Value = serde_json::json!({
-            "style": { "surface": { "relief": { "depth": 0.3, "light": 0.1 } } }
+        let retired: serde_json::Value = serde_json::json!({
+            "style": { "surface": { "relief": { "depth": 0.3 } } }
         });
-        prefer_relief_spellings(&mut both);
-        assert_eq!(both.pointer("/style/surface/relief"), Some(&serde_json::json!({ "light": 0.1 })), "light wins over depth");
+        let mut flat = String::new();
+        flatten_json_to_flat_props(&retired, "", &mut flat);
+        assert!(!flat.contains("bevel_depth"), "a retired `depth` flattens to no registry key: {flat}");
         let mut val: serde_json::Value = serde_json::json!({
             "style": { "surface": { "relief": {
-                "depth": 0.15,
+                "light": 0.15,
                 "height": 2.0, "wall": { "height": 5.0 },
                 "edge_profile": "old", "edge": { "profile": "new" },
                 "profile": "flat-only"
@@ -7031,7 +7030,7 @@ mod tests {
         assert_eq!(relief.pointer("/wall/height").and_then(|v| v.as_f64()), Some(5.0));
         assert_eq!(relief.pointer("/edge/profile").and_then(|v| v.as_str()), Some("new"));
         assert_eq!(relief.get("profile").and_then(|v| v.as_str()), Some("flat-only"), "no node spelling: kept");
-        assert_eq!(relief.get("depth").and_then(|v| v.as_f64()), Some(0.15), "no `light` beside it: the alias is kept");
+        assert_eq!(relief.get("light").and_then(|v| v.as_f64()), Some(0.15));
         let mut flat = String::new();
         flatten_json_to_flat_props(&val, "", &mut flat);
         assert_eq!(flat.lines().filter(|l| l.starts_with("bevel_height = ")).count(), 1, "{flat}");
diff --git a/src/scene/material.rs b/src/scene/material.rs
index 1604e26..f71ee70 100644
--- a/src/scene/material.rs
+++ b/src/scene/material.rs
@@ -632,7 +632,7 @@ mod tests {
                     frost compression=(f64)0.6 refraction=(f64)0.3
                     root corner_radius=(i64)24
                 }
-                relief depth=(f64)0.08
+                relief light=(f64)0.08
             }
         }
     "##;
@@ -649,7 +649,7 @@ mod tests {
                 plate material="glass" {
                     root corner_radius=(i64)24
                 }
-                relief depth=(f64)0.08
+                relief light=(f64)0.08
             }
         }
     "##;
@@ -773,7 +773,7 @@ mod tests {
                         }
                     }
                     plate blur=(bool)true material="plastic"
-                    relief depth=(f64)0.15 spec=(f64)0.5 shininess=(f64)20.0 curvature=(f64)0.1
+                    relief light=(f64)0.15 spec=(f64)0.5 shininess=(f64)20.0 curvature=(f64)0.1
                 }
                 control material="ghost"
             }