GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor: retire the flat frost keys — plate { frost … } is the one spelling
`style.surface.plate.blur` / `.radius` / `.backdrop_compression` /
`.refraction` were read as aliases of the `plate { frost radius=
compression= refraction= }` block since the block arrived earlier
today, and `backdrop_compression` was accepted inside any `frost` child
as an alias of `compression`. An alias that keeps working is a second
spelling forever, so the loader no longer reads any of them: a config
still carrying one is reported once by path (`color::retired_frost_keys`,
a warning that names the block spelling) and the key does nothing —
`blur=true` alone is sharp, and the warning is what says why. The
`plate_blur` flatten arm goes with them (no registry reader existed).
cce-relief writes the block — only for a frosted plate, since the block
is what frosts one — seeds its Frost sliders from the block, else from a
retired key it still finds (seed only), honours a legacy `blur` for the
frosted switch the same way, and removes the four retired keys on Save,
so a file migrates the first time it is saved. The two material
fixtures move onto the block; the frost-block test is now the
retirement's test (the retired keys flip neither the switch nor the
recipe), and `retired_frost_keys_are_named_by_path_and_the_block_is_not`
covers the report.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CLAUDE.md | 21 ++++++----
src/bin/cce-relief.rs | 63 +++++++++++++++++++----------
src/color.rs | 107 +++++++++++++++++++++++++++++++++++---------------
src/config.rs | 4 +-
src/layout.rs | 1 -
src/scene/material.rs | 35 +++++++++++------
6 files changed, 158 insertions(+), 73 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 46214c2..1522538 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -404,10 +404,17 @@ What this buys, and where the code is heading:
default plate material's recipe is spelled `style.surface.plate { frost
radius=5.5 compression=0 refraction=0 }` — the same `frost` child a named
material has — with a bare `frost` frosted at the defaults and `frost
- (bool)false` off. The four keys it replaces (`blur` as the switch,
- `radius`, `backdrop_compression`, `refraction`) are still read as aliases,
- and the block governs when both are present; cce-relief writes whichever
- spelling the config already uses. The enum variant is `Frost::Unfrosted`
+ (bool)false` off. The four keys it replaced (`blur` as the switch,
+ `radius`, `backdrop_compression`, `refraction`) were aliases for the rest
+ of that day and are RETIRED: the loader reports one it finds
+ (`color::retired_frost_keys`, a warning naming the path) and does not read
+ it — so a config with `blur=true` and no block is sharp, and the warning
+ is what says why. A material's `frost` child spells its compression
+ `compression` too; its `backdrop_compression` alias went with them.
+ cce-relief writes the block (only for a frosted plate, since the block is
+ what frosts one), seeds from a retired key it still finds, and removes
+ the retired keys on Save, so a file migrates the first time it is saved.
+ The enum variant is `Frost::Unfrosted`
(was `Opaque`): it means the plate never samples its backdrop, and a
translucent tint stays translucent with a SHARP view through it — which
is what the old name kept reading as "covers everything". The pane tint
@@ -427,7 +434,7 @@ What this buys, and where the code is heading:
"liquid glass" legibility problem, and why refraction and specular cannot
help: they are shape cues, and legibility is a luminance budget.
- `style.surface.plate.backdrop_compression` (0..1, `color::plate_backdrop_
+ `style.surface.plate { frost compression=… }` (0..1, `color::plate_backdrop_
compression`, **default 0** — every existing config keeps today's look)
remaps the blurred backdrop's luminance toward the plate's own key before
the tint, holding its chromaticity. It is not opacity and not "darken": it
@@ -483,12 +490,12 @@ What this buys, and where the code is heading:
the rung's legacy material (unset fields fall back; no `frost` child = opaque; a
binding wins over the legacy keys; an undefined name warns and degrades to legacy).
The DE finish's three fixed terms are `style.surface.relief.spec / shininess /
- curvature`; the default frost's blur sigma is `style.surface.plate.radius`. cce-relief
+ curvature`; the default frost's blur sigma is `plate { frost radius=… }`. cce-relief
edits them (Finish and Frost columns) and writes into the bound material's node or the
DE keys — never restructuring an unbound config. KDL trap when writing fixtures: two
nodes on one line need a `;`, and `a { b }` on one line is a parse error the loader
swallows into an empty document.
-- **`style.surface.plate.refraction` (0..1, default 0) is the rim, and it buys
+- **`plate { frost refraction=… }` (0..1, default 0) is the rim, and it buys
no legibility.** It is the answer to the other half of the question — not
"can I read this" but "is this an object". The roll is a real surface with a
real tilt, and `sv_rim` IS that tilt (the unnormalized normal's horizontal
diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 7701932..628fa5c 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -1155,22 +1155,27 @@ impl BevelPopup {
& w(&format!("{m}.frost.radius"), &radius)))
}
None => {
- // Into the `plate { frost … }` block when the config spells
- // it that way (the block governs, so a write to the old
- // scattered keys would be read and then overridden), else
- // the scattered keys the config already uses.
- let block = cce_ui::config::cached_config().pointer("/style/surface/plate/frost").is_some();
- let (kc, kr, kd) = if block {
- ("style.surface.plate.frost.compression", "style.surface.plate.frost.refraction", "style.surface.plate.frost.radius")
- } else {
- ("style.surface.plate.backdrop_compression", "style.surface.plate.refraction", "style.surface.plate.radius")
- };
+ // Into the `plate { frost … }` block — the one spelling the
+ // loader reads since the flat `blur` / `radius` /
+ // `backdrop_compression` / `refraction` keys were retired —
+ // and only for a FROSTED plate: writing the block is what
+ // frosts a plate, so an unfrosted config keeps its sharp
+ // view and the three sliders read as "when frosted", as they
+ // do for an opaque material node. A file that still carries
+ // a retired key had it read as the seed (below) and loses it
+ // here, so a config migrates on its first save.
+ let frosted = self.material_frosted;
+ let migrated = ["blur", "radius", "backdrop_compression", "refraction"]
+ .iter()
+ .all(|k| cce_ui::config::remove_config_value(p, &format!("style.surface.plate.{k}")));
w("style.surface.relief.spec", &spec)
& w("style.surface.relief.shininess", &shine)
& w("style.surface.relief.curvature", &curv)
- & w(kc, &comp)
- & w(kr, &refr)
- & w(kd, &radius)
+ & (!frosted
+ || (w("style.surface.plate.frost.compression", &comp)
+ & w("style.surface.plate.frost.refraction", &refr)
+ & w("style.surface.plate.frost.radius", &radius)))
+ & migrated
}
}
}
@@ -1518,12 +1523,26 @@ impl Application for BevelPopup {
None => Self::bound_material(),
};
let tdef = |k: &str| material_target.as_deref().map(|n| format!("/style/surface/material/{n}/{k}"));
+ // A `frost` block (anything but `frost (bool)false`) is frosted. The
+ // retired `plate.blur` is still honoured HERE, as a seed only: a
+ // file that has not been saved since the retirement opens frosted,
+ // and Save writes the block and drops the old key.
+ let block_frosted = |v: &serde_json::Value| {
+ match v.pointer("/style/surface/plate/frost") {
+ Some(serde_json::Value::Bool(on)) => *on,
+ Some(_) => true,
+ None => false,
+ }
+ };
+ let legacy_blur = |v: &serde_json::Value| {
+ tnum("/style/surface/plate/blur").map_or(
+ v.pointer("/style/surface/plate/blur").and_then(|b| b.as_bool()).unwrap_or(false),
+ |f| f > 0.001,
+ )
+ };
let material_frosted = match tj {
Some(v) => tdef("frost").is_some_and(|p| v.pointer(&p).is_some())
- || (material_target.is_none() && tnum("/style/surface/plate/blur").map_or(
- v.pointer("/style/surface/plate/blur").and_then(|b| b.as_bool()).unwrap_or(false),
- |f| f > 0.001,
- )),
+ || (material_target.is_none() && (block_frosted(v) || legacy_blur(v))),
None => material_target
.as_deref()
.map_or(pane.frost.is_frosted(), |n| cce_ui::color::named_material(n).is_some_and(|d| d.frost.is_some())),
@@ -1541,13 +1560,17 @@ impl Application for BevelPopup {
cce_ui::scene::Frost::Unfrosted => (0.0, 0.0, cce_ui::scene::Frost::DEFAULT_RADIUS),
},
};
- let frost_seed = |k: &str, plate_key: &str, process: f32| -> f32 {
+ // A frost knob seeds from the bound material's `frost` child, else
+ // the plate's `frost` block, else — seed only, so a not-yet-saved
+ // file opens where it was — the retired flat key under `plate`.
+ let frost_seed = |k: &str, legacy_key: &str, process: f32| -> f32 {
tdef(&format!("frost/{k}"))
.and_then(|p| tnum(&p))
- .or_else(|| tnum(&format!("/style/surface/plate/{plate_key}")))
+ .or_else(|| tnum(&format!("/style/surface/plate/frost/{k}")))
+ .or_else(|| tnum(&format!("/style/surface/plate/{legacy_key}")))
.unwrap_or(process)
};
- let comp0 = frost_seed("backdrop_compression", "backdrop_compression", pcomp);
+ let comp0 = frost_seed("compression", "backdrop_compression", pcomp);
let refr0 = frost_seed("refraction", "refraction", prefr);
let radius0 = frost_seed("radius", "radius", pradius);
let spec0 = finish_seed("spec", pane.finish.spec);
diff --git a/src/color.rs b/src/color.rs
index ac58098..efd3a10 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -681,35 +681,29 @@ fn parse_and_set_colors(content: &str) {
if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = true; }
}
- if let Some(c) = val
- .pointer("/style/surface/plate/backdrop_compression")
- .and_then(|v| v.as_f64())
- {
- if let Ok(mut lock) = PLATE_BACKDROP_COMPRESSION.write() { *lock = (c as f32).clamp(0.0, 1.0); }
- }
- if let Some(r) = val.pointer("/style/surface/plate/refraction").and_then(|v| v.as_f64()) {
- if let Ok(mut lock) = PLATE_REFRACTION.write() { *lock = (r as f32).clamp(0.0, 1.0); }
- }
- if let Some(blur) = val.pointer("/style/surface/plate/blur").and_then(|v| v.as_bool()) {
- if let Ok(mut lock) = PLATE_BLUR.write() { *lock = blur; }
- } else if let Some(blur_val) = val.pointer("/style/surface/plate/blur").and_then(|v| v.as_f64()) {
- if let Ok(mut lock) = PLATE_BLUR.write() { *lock = blur_val > 0.001; }
- }
- if let Some(r) = val.pointer("/style/surface/plate/radius").and_then(|v| v.as_f64()) {
- if let Ok(mut lock) = PLATE_FROST_RADIUS.write() { *lock = (r as f32).max(0.0); }
- }
- // The one-block spelling (2026-09-28), the same shape a named
- // material's `frost` child has: `plate { frost radius=5.5
- // compression=0 refraction=0 }` is frosted with those knobs, a bare
- // `frost` is frosted at the defaults, `frost (bool)false` is not
- // frosted. Read AFTER the four scattered keys (`blur`, `radius`,
- // `backdrop_compression`, `refraction`), which stay as aliases, so a
- // config that says both is governed by the block.
+ // The default plate's frost is ONE block, the same shape a named
+ // material's `frost` child has: `plate { frost radius=5.5 compression=0
+ // refraction=0 }` is frosted with those knobs, a bare `frost` is frosted
+ // at the defaults, `frost (bool)false` is not frosted. The four scattered
+ // keys it replaced on 2026-09-28 (`blur` as the switch, `radius`,
+ // `backdrop_compression`, `refraction`) were read as aliases for the rest
+ // of that day and are retired: a config still carrying one is reported
+ // (`retired_frost_keys`) and the key does nothing, because an alias that
+ // keeps working is a second spelling forever. The block is absent → not
+ // frosted, which is what `blur=true` alone now amounts to; the report
+ // is what tells the user why their plates went sharp.
+ let retired = retired_frost_keys(&val);
+ if !retired.is_empty() {
+ log::warn!(
+ "retired frost keys in config: {} — spell the frost as `plate {{ frost radius= compression= refraction= }}` (or a material's `frost` child with `compression`)",
+ retired.join(", ")
+ );
+ }
match val.pointer("/style/surface/plate/frost") {
Some(serde_json::Value::Object(fo)) => {
let num = |k: &str| fo.get(k).and_then(|v| v.as_f64()).map(|v| v as f32);
if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
- if let Some(c) = num("compression").or_else(|| num("backdrop_compression")) {
+ if let Some(c) = num("compression") {
if let Ok(mut lock) = PLATE_BACKDROP_COMPRESSION.write() { *lock = c.clamp(0.0, 1.0); }
}
if let Some(r) = num("refraction") {
@@ -752,7 +746,7 @@ fn parse_and_set_colors(content: &str) {
let Some(node) = node.as_object() else { continue };
let frost = node.get("frost").map(|fr| match fr.as_object() {
Some(fo) => FrostDef {
- compression: num(fo.get("backdrop_compression").or(fo.get("compression"))),
+ compression: num(fo.get("compression")),
refraction: num(fo.get("refraction")),
radius: num(fo.get("radius")),
},
@@ -785,6 +779,31 @@ fn parse_and_set_colors(content: &str) {
}
}
+/// The retired frost spellings a parsed config still carries, as the dotted
+/// paths a user would grep for: the four flat keys under `plate` (`blur`,
+/// `radius`, `backdrop_compression`, `refraction`) and `backdrop_compression`
+/// inside any `frost` child (the plate's, or a named material's), whose one
+/// name is `compression`. Empty for a clean config.
+pub fn retired_frost_keys(val: &serde_json::Value) -> Vec<String> {
+ let mut found = Vec::new();
+ for k in ["blur", "radius", "backdrop_compression", "refraction"] {
+ if val.pointer(&format!("/style/surface/plate/{k}")).is_some() {
+ found.push(format!("style.surface.plate.{k}"));
+ }
+ }
+ if val.pointer("/style/surface/plate/frost/backdrop_compression").is_some() {
+ found.push("style.surface.plate.frost.backdrop_compression".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"));
+ }
+ }
+ }
+ found
+}
+
fn load_colors_once() {
use std::sync::Once;
static INIT: Once = Once::new();
@@ -1921,8 +1940,8 @@ pub fn set_plate_refraction(r: f32) {
/// Whether the default plate material is frosted at all — the switch on
/// `Frost::from_style`. Spelled `style.surface.plate.frost` (a block, the
-/// knobs inside it; see the loader) or, as an alias, the older
-/// `style.surface.plate.blur` bool.
+/// knobs inside it; see the loader). The older `style.surface.plate.blur`
+/// bool is retired and reported, not read.
static PLATE_BLUR: RwLock<bool> = RwLock::new(false);
/// The finish's three fixed terms — specular strength, shininess exponent,
@@ -1935,8 +1954,8 @@ static FINISH_SPEC: RwLock<f32> = RwLock::new(0.4);
static FINISH_SHININESS: RwLock<f32> = RwLock::new(24.0);
static FINISH_CURVATURE: RwLock<f32> = RwLock::new(0.2);
-/// The default material's blur radius (`style.surface.plate.radius`, the
-/// kernel sigma in logical px) — [`crate::scene::Frost::DEFAULT_RADIUS`]
+/// The default material's blur radius (`style.surface.plate.frost.radius`,
+/// the kernel sigma in logical px) — [`crate::scene::Frost::DEFAULT_RADIUS`]
/// unless config says otherwise.
static PLATE_FROST_RADIUS: RwLock<f32> = RwLock::new(crate::scene::material::Frost::DEFAULT_RADIUS);
@@ -2231,6 +2250,32 @@ mod tests {
assert_eq!(plate_bevel_width(), crate::layout::bevel_width(), "cleared: back to the relief width");
}
+ /// The four flat frost keys and the `backdrop_compression` spelling
+ /// inside a `frost` child are reported by path; the block itself and a
+ /// material's `compression` are not.
+ #[test]
+ fn retired_frost_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 } } }
+ } } });
+ assert!(retired_frost_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,
+ "frost": { "backdrop_compression": 0.2 } },
+ "material": { "glass": { "frost": { "backdrop_compression": 0.6 } } }
+ } } });
+ assert_eq!(retired_frost_keys(&old), vec![
+ "style.surface.plate.blur",
+ "style.surface.plate.radius",
+ "style.surface.plate.backdrop_compression",
+ "style.surface.plate.refraction",
+ "style.surface.plate.frost.backdrop_compression",
+ "style.surface.material.glass.frost.backdrop_compression",
+ ]);
+ assert!(retired_frost_keys(&serde_json::json!({})).is_empty());
+ }
+
#[test]
fn plate_refraction_defaults_off_and_clamps() {
assert_eq!(plate_refraction(), 0.0, "off unless a config asks");
@@ -2247,7 +2292,7 @@ mod tests {
/// Off is load-bearing: it is the behaviour every config already in the
/// wild has, and a toolkit-wide default that changed how every frosted
/// surface in the DE looks would arrive unannounced in eighteen apps.
- /// Opting in is a per-app `style.surface.plate.backdrop_compression`.
+ /// Opting in is a per-app `style.surface.plate { frost compression=… }`.
#[test]
fn backdrop_compression_defaults_off_and_clamps() {
assert_eq!(plate_backdrop_compression(), 0.0, "off unless a config asks");
diff --git a/src/config.rs b/src/config.rs
index b808e8f..68d0144 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -887,14 +887,14 @@ mod tests {
fn material_keys_write_as_frost_and_finish_props() {
use super::{parse_kdl_to_json, update_kdl_in_memory};
let mut doc = kdl::KdlDocument::new();
- assert!(update_kdl_in_memory(&mut doc, "style.surface.material.glass.frost.backdrop_compression", "0.6", "style"));
+ assert!(update_kdl_in_memory(&mut doc, "style.surface.material.glass.frost.compression", "0.6", "style"));
assert!(update_kdl_in_memory(&mut doc, "style.surface.material.glass.frost.refraction", "0.3", "style"));
assert!(update_kdl_in_memory(&mut doc, "style.surface.material.glass.finish.spec", "0.4", "style"));
assert!(update_kdl_in_memory(&mut doc, "style.surface.material.glass.color", "#05050840", "style"));
assert!(update_kdl_in_memory(&mut doc, "style.surface.plate.material", "glass", "style"));
let text = doc.to_string();
let val = parse_kdl_to_json(&text);
- assert_eq!(val.pointer("/style/surface/material/glass/frost/backdrop_compression").and_then(|v| v.as_f64()), Some(0.6), "{text}");
+ assert_eq!(val.pointer("/style/surface/material/glass/frost/compression").and_then(|v| v.as_f64()), Some(0.6), "{text}");
assert_eq!(val.pointer("/style/surface/material/glass/frost/refraction").and_then(|v| v.as_f64()), Some(0.3));
assert_eq!(val.pointer("/style/surface/material/glass/finish/spec").and_then(|v| v.as_f64()), Some(0.4));
assert_eq!(val.pointer("/style/surface/material/glass/color").and_then(|v| v.as_str()), Some("#05050840"));
diff --git a/src/layout.rs b/src/layout.rs
index e443c8b..9437d90 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -363,7 +363,6 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
"style.surface.plate.color" => "plate_color",
"style.surface.plate.border_color" => "plate_border_color",
"style.surface.plate.border_thickness" => "plate_border_thickness",
- "style.surface.plate.blur" => "plate_blur",
"input.touchpad.natural_scroll" => "touchpad_natural_scroll",
other => {
diff --git a/src/scene/material.rs b/src/scene/material.rs
index 56ed111..d4fb58c 100644
--- a/src/scene/material.rs
+++ b/src/scene/material.rs
@@ -628,7 +628,8 @@ mod tests {
style {
surface {
param color=(rgba)"#05050840"
- plate backdrop_compression=(f64)0.6 refraction=(f64)0.3 bevel_width=(f64)12.0 blur=(bool)true {
+ plate bevel_width=(f64)12.0 {
+ frost compression=(f64)0.6 refraction=(f64)0.3
root corner_radius=(i64)24
}
relief depth=(f64)0.08
@@ -642,7 +643,7 @@ mod tests {
material {
glass {
color (rgba)"#05050840"
- frost backdrop_compression=(f64)0.6 refraction=(f64)0.3
+ frost compression=(f64)0.6 refraction=(f64)0.3
}
}
plate material="glass" bevel_width=(f64)12.0 {
@@ -693,28 +694,38 @@ mod tests {
assert_eq!(crate::color::material_binding(PlateRung::Pane), None);
}
- /// The default material's frost as ONE block — `plate { frost radius=…
+ /// The default material's frost is ONE block — `plate { frost radius=…
/// compression=… refraction=… }`, the shape a named material's `frost`
- /// child already had — with the four scattered keys (`blur`, `radius`,
- /// `backdrop_compression`, `refraction`) still read as aliases: a bare
- /// `frost` is frosted at the defaults, `frost (bool)false` is not, and
- /// when both spellings are present the block governs.
+ /// child already had: a bare `frost` is frosted at the defaults, `frost
+ /// (bool)false` is not. The four scattered keys it replaced (`blur`,
+ /// `radius`, `backdrop_compression`, `refraction`) are RETIRED, not
+ /// aliases: a config carrying one is reported by path
+ /// (`color::retired_frost_keys`) and the key does nothing — `blur=true`
+ /// alone is sharp, a flat `radius` moves nothing, and the old knob name
+ /// inside the block is ignored too.
#[test]
- fn the_frost_block_is_one_spelling_of_the_default_recipe() {
+ fn the_frost_block_is_the_only_spelling_of_the_default_recipe() {
let _lock = crate::color::test_color_state_lock();
crate::layout::lazy_init_style_registry();
let _ = crate::color::plate_blur();
+ let doc = |plate: &str| format!("style {{\n surface {{\n plate {{\n {plate}\n }}\n }}\n}}\n");
let load = |plate: &str| {
- crate::color::reload_colors(&format!("style {{\n surface {{\n plate {{\n {plate}\n }}\n }}\n}}\n"));
+ crate::color::reload_colors(&doc(plate));
(crate::color::plate_blur(), Frost::from_style())
};
+ let retired = |plate: &str| crate::color::retired_frost_keys(&crate::config::parse_kdl_to_json(&doc(plate)));
let frosted = |c: f32, r: f32, rad: f32| Frost::Frosted { compression: c, refraction: r, radius: rad };
assert_eq!(load("frost radius=(f64)3.0 compression=(f64)0.4 refraction=(f64)0.1"), (true, frosted(0.4, 0.1, 3.0)));
- assert_eq!(load("frost backdrop_compression=(f64)0.2"), (true, frosted(0.2, 0.1, 3.0)), "the old knob name is accepted inside the block; unset knobs keep their last value");
+ assert!(retired("frost radius=(f64)3.0 compression=(f64)0.4 refraction=(f64)0.1").is_empty());
+ assert_eq!(load("frost backdrop_compression=(f64)0.2"), (true, frosted(0.4, 0.1, 3.0)), "the old knob name inside the block is ignored; unset knobs keep their last value");
+ assert_eq!(retired("frost backdrop_compression=(f64)0.2"), vec!["style.surface.plate.frost.backdrop_compression"]);
assert_eq!(load("frost").0, true, "a bare `frost` is frosted");
assert_eq!(load("frost (bool)false").0, false);
- assert_eq!(load("blur (bool)true\n radius (f64)2.0"), (true, frosted(0.2, 0.1, 2.0)), "the scattered spelling still works");
- assert_eq!(load("blur (bool)false\n frost compression=(f64)0.7"), (true, frosted(0.7, 0.1, 2.0)), "the block governs");
+ // `from_style` is the RECIPE, `plate_blur` the switch: the retired
+ // keys flip neither — the switch stays off and the radius stays 3.
+ assert_eq!(load("blur (bool)true\n radius (f64)2.0"), (false, frosted(0.4, 0.1, 3.0)), "the retired spelling frosts nothing and moves nothing");
+ assert_eq!(retired("blur (bool)true\n radius (f64)2.0"), vec!["style.surface.plate.blur", "style.surface.plate.radius"]);
+ assert_eq!(load("blur (bool)false\n frost compression=(f64)0.7"), (true, frosted(0.7, 0.1, 3.0)), "the block is read, the retired key is not");
crate::color::reload_colors("");
}