From 727cd52c3b140cbb9c65929ce013ece1372a8d0d Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Wed, 29 Jul 2026 22:25:42 -0400 Subject: [PATCH] fix(types): trim class names in Node::add_class The doc comment promised blank names were ignored, but the guard only rejected the empty string. Stylesheet selectors match class names exactly, so a padded name would sit in `classes` and match no rule. No current caller can pass one: the parser splits on whitespace, and the subgraph and import paths strip everything but alphanumerics and hyphens. This makes the public contract on the shared type match what it claims. Co-Authored-By: Claude Opus 5 (1M context) --- lib/foundation/fabro-types/src/graph.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/foundation/fabro-types/src/graph.rs b/lib/foundation/fabro-types/src/graph.rs index 9fdec1ff2..e2ed3fab4 100644 --- a/lib/foundation/fabro-types/src/graph.rs +++ b/lib/foundation/fabro-types/src/graph.rs @@ -155,9 +155,14 @@ impl Node { /// enclosing subgraphs, and import placeholders — so every caller needs the /// same de-duplicating append. /// + /// The name is trimmed, and a name that is empty or only whitespace is + /// dropped. Stylesheet selectors match class names exactly, so a padded + /// name would never match any rule. + /// /// Order is preserved because the first class is meaningful: it supplies /// the fallback thread ID for fidelity threading. pub fn add_class(&mut self, class: &str) { + let class = class.trim(); if !class.is_empty() && !self.classes.iter().any(|existing| existing == class) { self.classes.push(class.to_string()); } @@ -684,6 +689,18 @@ mod tests { assert!(node.project_memory()); } + #[test] + fn add_class_trims_names_and_drops_blanks_and_duplicates() { + let mut node = Node::new("work"); + node.add_class("coding"); + node.add_class(" coding "); + node.add_class(""); + node.add_class(" "); + node.add_class("\tcritical\n"); + + assert_eq!(node.classes, ["coding", "critical"]); + } + fn node_with(id: &str, attrs: &[(&str, &str)]) -> Node { let mut node = Node::new(id); for (key, value) in attrs {