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

commitdcd01c013f7d1b4b116f242c5adce4893e05e66f
parent9f2775e3d2
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-28 14:04
refactor: style.surface.relief.light is the spelling; depth is its alias

`depth` was the key every config carried for the relief's light
strength, with `light` — the honest name, since the value is not a
length — read as an alias. The two swap roles: `light` is the spelling
cce-relief writes (Save takes `depth` off the file, as it does the other
pre-rename keys), the docs and the flatten table name it first, and
`prefer_relief_spellings` (was `prefer_relief_wall_edge`, now also the
one flat pair) lets `light` win when a file carries both, so the choice
is never left to line order. `depth` and the `window_manager.bevel_depth`
compat spelling stay as aliases; the registry key is `bevel_depth` as
before. The precedence test grows the light-over-depth case.

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

 CLAUDE.md             | 14 ++++++-----
 src/bin/cce-relief.rs | 17 ++++++++------
 src/layout.rs         | 64 ++++++++++++++++++++++++++++++++-------------------
 3 files changed, 58 insertions(+), 37 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 1522538..86ee805 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -686,16 +686,18 @@ multiply on the plate's own fill plus a specular crest. Each is a node under
 `style.surface.relief` with the same three keys:
 
 ```kdl
-relief depth=0.15 width=9.3 {
+relief light=0.15 width=9.3 {
     wall height=(mm)0.3 profile="smooth;…"
     edge height=4.0    profile="smooth;…"
 }
 ```
 
 `width` (the run of both, one number — see the roll-width note below) and
-`depth` (the light; `light` is its honest alias — NOT a length, it is
-`bevel_depth` → `Finish.strength`) stay on the node itself, since both
-shapes share them. `height` is a length — the wall's drop, the edge's rise —
+`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 —
 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
@@ -707,9 +709,9 @@ downstream of the registry knows. Until 2026-09-28 the keys were flat on the
 node with the wall UNNAMED (`height`, `profile`) and the edge prefixed
 (`edge_height`, `edge_profile`), which read as one shape with an "edge"
 variant rather than two shapes; those spellings survive as aliases in
-`layout.rs`'s flatten table, and `prefer_relief_wall_edge` drops a flat one
+`layout.rs`'s flatten table, and `prefer_relief_spellings` drops a flat one
 whenever its node spelling is present, so a file carrying both is decided by
-the node and not by line order (`the_node_spelling_of_a_relief_key_wins_over_the_flat_one`).
+the node and not by line order (`the_current_spelling_of_a_relief_key_wins_over_the_legacy_one`).
 `cce-relief`'s Save writes the node spellings and REMOVES the flat ones
 (`config::remove_config_value`), so a file migrates the first time it is
 saved; `wall` and `edge` are `PROP_NODES` members so their keys land as
diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 628fa5c..dada93f 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -19,8 +19,9 @@
 //! hot.
 //!
 //! The knobs under the section: the wall curve's Shoulder / Base / Bias,
-//! then **Light** (the `bevel_depth` slot — how hard the light falls across
-//! the wall; not a length), **Width** (the wall's run, logical px) and
+//! then **Light** (`style.surface.relief.light`, the `bevel_depth` registry
+//! slot — how hard the light falls across the wall; not a length), **Width**
+//! (the wall's run, logical px) and
 //! **Height** (the wall's drop, logical px; 0 = follow the width at the
 //! analytic ratio). Height is the fabrication axis: the section's depth
 //! numbers read in millimetres whenever the display metric is real
@@ -1327,19 +1328,21 @@ impl BevelPopup {
         // The two shapes are nodes — `relief { wall … ; edge … }` — and a
         // save MIGRATES: the flat spellings this editor wrote until
         // 2026-09-28 (`height`, `profile`, `profile_knobs` for the wall,
-        // `edge_*` for the edge) come off the file, or the node spelling
-        // would shadow a stale line forever; and the knob triples come off
-        // under either spelling, since they live in the state file now.
+        // `edge_*` for the edge, and `depth` for the light) come off the
+        // file, or a current spelling would shadow a stale line forever; and
+        // the knob triples come off under either spelling, since they live
+        // in the state file now.
         // `remove_config_value` is true for a key that is not there, so a
         // clean file costs nothing.
         let ok = height_ok
             & material_ok
             & knobs_ok
-            & w("style.surface.relief.depth", &depth)
+            & w("style.surface.relief.light", &depth)
             & w("style.surface.relief.width", &width)
             & w("style.surface.relief.wall.profile", &self.wall.last_spec)
             & w("style.surface.relief.edge.profile", &self.edge.last_spec);
         let migrated = [
+            "depth",
             "height", "edge_height", "profile", "edge_profile",
             "profile_knobs", "edge_knobs", "wall.knobs", "edge.knobs",
         ]
@@ -1412,7 +1415,7 @@ impl Application for BevelPopup {
         // then the flat legacy spelling a file saved before 2026-09-28 still
         // carries (`height` / `profile` / `profile_knobs` for the wall,
         // `edge_*` for the edge) — the same precedence the registry load
-        // applies through `layout::prefer_relief_wall_edge`.
+        // applies through `layout::prefer_relief_spellings`.
         let rel_shape = |node: &str, k: &str, legacy: &str| {
             let r = target_relief.as_ref()?;
             r.get(node).and_then(|n| n.get(k)).or_else(|| r.get(legacy)).cloned()
diff --git a/src/layout.rs b/src/layout.rs
index 9437d90..35d46c8 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -243,9 +243,11 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 // (these shade every bevel/boss/recess in the toolkit — the
                 // compositor never read them, so the old window_manager
                 // spelling survives only as a compat alias).
-                // `depth` is the light strength, not a length — `light` is
-                // the honest spelling, `depth` the one every config has.
-                "style.surface.relief.depth" | "style.surface.relief.light" | "window_manager.bevel_depth" => "bevel_depth",
+                // `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",
                 "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,
@@ -257,7 +259,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 // keys keep their old names. The flat spellings on the
                 // second line of each pair are the pre-rename aliases
                 // (`height` / `profile` were the wall's, `edge_*` the
-                // edge's); `prefer_relief_wall_edge` drops a flat one
+                // edge's); `prefer_relief_spellings` drops a flat one
                 // whenever its node spelling is present, so a config
                 // carrying both is decided by the node, not by line order.
                 // cce-relief's slider knobs are NOT a style key any more
@@ -393,36 +395,44 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
     }
 }
 
-/// The `wall` / `edge` node spellings of the relief geometry win over the
-/// flat legacy ones. Both flatten 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.
-pub(crate) fn prefer_relief_wall_edge(val: &mut serde_json::Value) {
-    const PAIRS: &[(&str, &str, &str)] = &[
+/// 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
+/// 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.
+pub(crate) fn prefer_relief_spellings(val: &mut serde_json::Value) {
+    const NODE_PAIRS: &[(&str, &str, &str)] = &[
         ("height", "wall", "height"),
         ("profile", "wall", "profile"),
         ("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;
     };
-    for (flat, node, key) in PAIRS {
+    for (flat, node, key) in NODE_PAIRS {
         if relief.get(*node).and_then(|n| n.get(*key)).is_some() {
             relief.remove(*flat);
         }
     }
+    for (legacy, current) in FLAT_PAIRS {
+        if relief.get(*current).is_some() {
+            relief.remove(*legacy);
+        }
+    }
 }
 
 fn read_config() -> Option<String> {
     let path = crate::config::get_config_path();
     if let Ok(content) = std::fs::read_to_string(&path) {
         let mut val = crate::config::parse_kdl_to_json(&content);
-        prefer_relief_wall_edge(&mut val);
+        prefer_relief_spellings(&mut val);
         let mut flat_props = String::new();
         flatten_json_to_flat_props(&val, "", &mut flat_props);
         return Some(flat_props);
@@ -434,7 +444,7 @@ pub fn read_config_value(target_key: &str) -> Option<String> {
     let path = crate::config::get_config_path();
     if let Ok(content) = std::fs::read_to_string(&path) {
         let mut val = crate::config::parse_kdl_to_json(&content);
-        prefer_relief_wall_edge(&mut val);
+        prefer_relief_spellings(&mut val);
         let mut flat_props = String::new();
         flatten_json_to_flat_props(&val, "", &mut flat_props);
         for line in flat_props.lines() {
@@ -6995,11 +7005,17 @@ mod tests {
     }
 
     /// A config carrying both spellings of one relief key is decided by the
-    /// node spelling, whichever line the file wrote first — the flat pass
-    /// loads in JSON order, and `prefer_relief_wall_edge` is what makes the
-    /// order irrelevant. Keys with no node spelling are left alone.
+    /// 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.
     #[test]
-    fn the_node_spelling_of_a_relief_key_wins_over_the_flat_one() {
+    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 } } }
+        });
+        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 val: serde_json::Value = serde_json::json!({
             "style": { "surface": { "relief": {
                 "depth": 0.15,
@@ -7008,21 +7024,21 @@ mod tests {
                 "profile": "flat-only"
             } } }
         });
-        prefer_relief_wall_edge(&mut val);
+        prefer_relief_spellings(&mut val);
         let relief = val.pointer("/style/surface/relief").unwrap();
         assert!(relief.get("height").is_none(), "the flat wall height yields: {relief}");
         assert!(relief.get("edge_profile").is_none(), "the flat edge profile yields: {relief}");
         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));
+        assert_eq!(relief.get("depth").and_then(|v| v.as_f64()), Some(0.15), "no `light` beside it: the alias is kept");
         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}");
         assert!(flat.contains("bevel_height = 5"), "{flat}");
         // A config with no relief block at all is untouched.
         let mut none: serde_json::Value = serde_json::json!({ "style": {} });
-        prefer_relief_wall_edge(&mut none);
+        prefer_relief_spellings(&mut none);
         assert_eq!(none, serde_json::json!({ "style": {} }));
     }