Skip to main content

brep_render/
assembly_status.rs

1//! The ONE assembly-constraint status → label/color map (requirements §5
2//! vocabulary). Both the Assembly Constraints panel's row status labels AND the
3//! viewport overlay labels (lane G) read THIS module — the status vocabulary and
4//! its colors are never re-declared anywhere else (the UI-consistency directive).
5//!
6//! Colors are the captured palette: grey `#8e8e93` (disabled), green `#30d158`
7//! (satisfied), yellow `#ffd60a` (adjusting / everything indeterminate — also
8//! the default), red `#ff3b30` (blocked / duplicate / error / failed). Exposed
9//! as plain sRGB byte triples so this stays egui-free (reusable by any painter).
10
11/// The human label for a constraint `persistentData.status` word. Unknown /
12/// empty statuses read as "Pending" (a constraint that has never run).
13pub fn status_label(status: &str) -> &'static str {
14    match status {
15        "disabled" => "Disabled",
16        "satisfied" => "Satisfied",
17        "adjusted" | "adjusting" => "Adjusting",
18        "blocked" => "Blocked",
19        "pending" | "" => "Pending",
20        "duplicate" => "Duplicate",
21        "error" => "Error",
22        "incomplete" => "Incomplete",
23        "invalid-selection" => "Invalid selection",
24        "unsupported-selection" | "unimplemented" => "Unimplemented",
25        "pending-component" => "Pending component",
26        "fixed" => "Locked",
27        "noop" => "No change",
28        "apply-failed" => "Failed",
29        _ => "Pending",
30    }
31}
32
33/// The status color as sRGB bytes. Grey for disabled, green for satisfied,
34/// red for the error family (blocked / duplicate / error / apply-failed),
35/// yellow (`#ffd60a`) for adjusting and every indeterminate status — the
36/// captured default.
37pub fn status_color_rgb(status: &str) -> [u8; 3] {
38    match status {
39        "disabled" => [0x8e, 0x8e, 0x93],
40        "satisfied" => [0x30, 0xd1, 0x58],
41        "blocked" | "duplicate" | "error" | "invalid-selection" | "apply-failed" => {
42            [0xff, 0x3b, 0x30]
43        }
44        "unsupported-selection" | "unimplemented" => [0xff, 0x9f, 0x0a],
45        _ => [0xff, 0xd6, 0x0a],
46    }
47}
48
49/// The status color as a CSS hex string (`#rrggbb`) — the overlay/label lanes
50/// that speak CSS-hex use this so the byte triple and the hex can never drift.
51pub fn status_color_hex(status: &str) -> String {
52    let [r, g, b] = status_color_rgb(status);
53    format!("#{r:02x}{g:02x}{b:02x}")
54}
55
56/// Rank a status for the structure tree's per-component ROLLUP dot: the WORST
57/// status across a component's constraints wins. Higher = worse. Order:
58/// error family (3) > indeterminate/adjusting (2) > satisfied (1) >
59/// disabled (0).
60pub fn status_severity(status: &str) -> u8 {
61    match status {
62        "disabled" => 0,
63        "satisfied" => 1,
64        "blocked" | "duplicate" | "error" | "apply-failed" => 3,
65        _ => 2,
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn vocabulary_labels_match_requirements() {
75        // The requirements-§5 table, spot-checked across every family.
76        assert_eq!(status_label("disabled"), "Disabled");
77        assert_eq!(status_label("satisfied"), "Satisfied");
78        assert_eq!(status_label("adjusted"), "Adjusting");
79        assert_eq!(status_label("adjusting"), "Adjusting");
80        assert_eq!(status_label("blocked"), "Blocked");
81        assert_eq!(status_label("pending"), "Pending");
82        assert_eq!(status_label("duplicate"), "Duplicate");
83        assert_eq!(status_label("error"), "Error");
84        assert_eq!(status_label("incomplete"), "Incomplete");
85        assert_eq!(status_label("invalid-selection"), "Invalid selection");
86        assert_eq!(status_label("unsupported-selection"), "Unimplemented");
87        assert_eq!(status_label("unimplemented"), "Unimplemented");
88        assert_eq!(status_label("pending-component"), "Pending component");
89        assert_eq!(status_label("fixed"), "Locked");
90        assert_eq!(status_label("noop"), "No change");
91        assert_eq!(status_label("apply-failed"), "Failed");
92        // Never-ran / unknown default to Pending.
93        assert_eq!(status_label(""), "Pending");
94        assert_eq!(status_label("???"), "Pending");
95    }
96
97    #[test]
98    fn colors_match_the_captured_palette() {
99        assert_eq!(status_color_hex("disabled"), "#8e8e93");
100        assert_eq!(status_color_hex("satisfied"), "#30d158");
101        assert_eq!(status_color_hex("adjusted"), "#ffd60a");
102        assert_eq!(status_color_hex("blocked"), "#ff3b30");
103        assert_eq!(status_color_hex("duplicate"), "#ff3b30");
104        assert_eq!(status_color_hex("error"), "#ff3b30");
105        assert_eq!(status_color_hex("apply-failed"), "#ff3b30");
106        // The default is the captured yellow.
107        assert_eq!(status_color_hex("pending"), "#ffd60a");
108        assert_eq!(status_color_hex("fixed"), "#ffd60a");
109        assert_eq!(status_color_hex(""), "#ffd60a");
110        // Byte triple and hex agree by construction.
111        assert_eq!(status_color_rgb("satisfied"), [0x30, 0xd1, 0x58]);
112    }
113
114    #[test]
115    fn severity_orders_worst_first() {
116        assert!(status_severity("error") > status_severity("pending"));
117        assert!(status_severity("blocked") > status_severity("satisfied"));
118        assert!(status_severity("satisfied") > status_severity("disabled"));
119        assert_eq!(status_severity("adjusted"), status_severity("incomplete"));
120    }
121}