GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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": {} }));
}