Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-demo/src/errcode_tab.rs

//! **The `Errors` tab** — the facett **UI error-code** showcase.
//!
//! This is the visible proof of the `facet-<component>-<n>` scheme
//! ([`facett_core::errcode`]). It:
//!
//! 1. **Triggers a few example errors** and renders them so the CLEAR-PINK code
//!    chips are visible on-screen (in a debug build; subdued in release).
//! 2. Shows the running build's **version + git hash** (the nordisk WHICH-build LAW).
//! 3. Renders the **error-code MATRIX slide** — the whole [`facett_core::errcode::REGISTRY`]
//!    as a table (component → code → message → source link → Phase-2 consumer
//!    reaction) — the same catalog that grows the reaction-test coverage in Phase 2.
//! 4. Shows a **source link per facet-component**.
//! 5. Demonstrates the **consumer mount-prefix** API
//!    (`korp/cases:facet-map-2` vs `korp/analysis:facet-map-2`).
//!
//! Everything is folded into `state_json` so the robot-UI matrix keys off the CODE,
//! not a matched string. The tab itself reports [`Severity::Info`](facett_core::Severity)
//! — it displays error codes as a **catalog**, it is not itself in a failure state, so
//! it does not (and must not) trip the Robot-UI HARD GATE. It uses
//! [`FacetError::render_demo`](facett_core::errcode::FacetError::render_demo) (neutral
//! a11y atoms), never the live [`render`](facett_core::errcode::FacetError::render).

use facett::Facet;
use facett::core::FacetCaps;
use facett::core::errcode::{self, FacetError};

/// The stable tab title (the deck / discovery-contract key + the legend key).
pub const TITLE: &str = "Errors";

/// The error-code showcase tab.
pub struct ErrCodeTab {
    title: &'static str,
    /// The example errors triggered for the pink-code demonstration.
    raised: Vec<FacetError>,
}

impl ErrCodeTab {
    pub fn new(title: &'static str) -> Self {
        // Trigger a couple of demo error sites so the pink codes are on-screen. These
        // are the `demo` component's registered codes (facet-demo-1 / facet-demo-2).
        let raised = vec![
            facett::core::facet_err!(demo, 1, "example error: a triggered demo condition (showcase)"),
            facett::core::facet_err!(demo, 2, "example note: a triggered demo degrade (showcase)"),
        ];
        Self { title, raised }
    }

    /// The build stamp line (version + git hash).
    fn version_line(&self) -> String {
        crate::version_line()
    }
}

impl Facet for ErrCodeTab {
    fn title(&self) -> &str {
        self.title
    }

    fn ui(&mut self, ui: &mut egui::Ui) {
        egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
            ui.heading("facett UI error codes");
            ui.label("Every facett UI part carries a STABLE, UNIQUE code facet-<component>-<n> so tests + consumers react to the CODE, not a matched string.");
            // The WHICH-build stamp (git hash) — also shown in the About popup.
            ui.weak(self.version_line());
            ui.separator();

            // ── 1. Triggered examples: the pink code chips (visible in debug) ──────
            ui.strong("Triggered examples (message + pink code):");
            for e in &self.raised {
                e.render_demo(ui);
            }
            ui.small(if cfg!(debug_assertions) {
                "(debug build — codes render in CLEAR PINK next to the message)"
            } else {
                "(release build — codes subdued on-screen but present in state_json + logs)"
            });
            ui.separator();

            // ── 2. Consumer mount-prefix demonstration ────────────────────────────
            ui.strong("Consumer mount-prefix (Phase 2 — same facet, two mount points):");
            if let Some(e) = self.raised.first() {
                let cases = e.with_consumer_prefix("korp/cases");
                let analysis = e.with_consumer_prefix("korp/analysis");
                ui.monospace(cases.mounted_code());
                ui.monospace(analysis.mounted_code());
            }
            ui.separator();

            // ── 3. The error-code MATRIX slide (the whole registry) ───────────────
            ui.strong(format!(
                "Error-code MATRIX — {} codes across {} components:",
                errcode::registry().len(),
                errcode::components().len()
            ));
            egui::Grid::new("errcode_matrix")
                .striped(true)
                .num_columns(7)
                .spacing([14.0, 4.0])
                .show(ui, |ui| {
                    ui.strong("component");
                    ui.strong("code");
                    ui.strong("message");
                    ui.strong("source");
                    ui.strong("status");
                    ui.strong("reaction (Phase 2)");
                    ui.strong("who reacts");
                    ui.end_row();
                    for e in errcode::registry() {
                        ui.label(e.component);
                        ui.label(
                            egui::RichText::new(e.code)
                                .monospace()
                                .color(errcode::code_color()),
                        );
                        ui.label(e.message);
                        ui.hyperlink_to(format!("{}/{}", e.crate_dir, e.src_file), e.source_url());
                        ui.label(if errcode::is_wired(e.code) { "wired" } else { "reserved" });
                        // The canonical consumer REACTION — the Phase-2 column.
                        ui.label(egui::RichText::new(e.reaction.token()).monospace())
                            .on_hover_text(e.reaction.describe());
                        ui.label(if e.consumer.is_empty() { "— (none today)" } else { e.consumer });
                        ui.end_row();
                    }
                });

            ui.separator();
            // ── The Phase-2 REACTION MATRIX slide: one row per (code × mount) pair ──
            let matrix = errcode::reaction_matrix();
            ui.strong(format!(
                "Reaction MATRIX (Phase 2) — {} (code × mount) pairs the consumer tests assert:",
                matrix.len()
            ));
            egui::Grid::new("errcode_reaction_matrix")
                .striped(true)
                .num_columns(4)
                .spacing([14.0, 4.0])
                .show(ui, |ui| {
                    ui.strong("mounted code");
                    ui.strong("base code");
                    ui.strong("mount");
                    ui.strong("expected reaction");
                    ui.end_row();
                    for (mounted, base, mount, reaction) in &matrix {
                        ui.label(
                            egui::RichText::new(mounted).monospace().color(errcode::code_color()),
                        );
                        ui.label(egui::RichText::new(*base).monospace());
                        ui.label(*mount);
                        ui.label(egui::RichText::new(reaction.token()).monospace())
                            .on_hover_text(reaction.describe());
                        ui.end_row();
                    }
                });
        });
    }

    fn state_json(&self) -> serde_json::Value {
        // The whole catalog as data — a robot test keys off the CODE.
        let matrix: Vec<serde_json::Value> = errcode::registry()
            .iter()
            .map(|e| {
                serde_json::json!({
                    "component": e.component,
                    "n": e.n,
                    "code": e.code,
                    "message": e.message,
                    "source_url": e.source_url(),
                    "consumer": e.consumer,
                    // Phase 2: the canonical reaction + whether the code is raised today.
                    "reaction": e.reaction.token(),
                    "status": if errcode::is_wired(e.code) { "wired" } else { "reserved" },
                })
            })
            .collect();
        // The Phase-2 reaction MATRIX — one row per (code × mount) pair, the exact
        // table the consumer reaction tests are generated from.
        let reaction_matrix: Vec<serde_json::Value> = errcode::reaction_matrix()
            .into_iter()
            .map(|(mounted, base, mount, reaction)| {
                serde_json::json!({
                    "mounted_code": mounted,
                    "code": base,
                    "mount": mount,
                    "reaction": reaction.token(),
                })
            })
            .collect();
        // The triggered examples (code + message + url) — proof the pink render ran.
        let raised: Vec<serde_json::Value> = self.raised.iter().map(|e| e.to_json()).collect();
        // The consumer-prefix demonstration.
        let mount = self
            .raised
            .first()
            .map(|e| {
                serde_json::json!({
                    "base": e.code,
                    "cases": e.with_consumer_prefix("korp/cases").mounted_code(),
                    "analysis": e.with_consumer_prefix("korp/analysis").mounted_code(),
                })
            })
            .unwrap_or(serde_json::Value::Null);

        serde_json::json!({
            "scheme": "facet-<component>-<n>",
            "version": self.version_line(),
            "git_hash": env!("FACETT_GIT_HASH"),
            "code_count": errcode::registry().len(),
            "component_count": errcode::components().len(),
            "components": errcode::components(),
            "raised": raised,
            "mount_example": mount,
            "matrix": matrix,
            // Phase 2: the (code × mount) reaction matrix + the vocabulary.
            "reaction_matrix": reaction_matrix,
            "reaction_pairs": errcode::reaction_matrix().len(),
            "wired_count": errcode::registry().iter().filter(|e| errcode::is_wired(e.code)).count(),
            // The visible-pink flag (decision 4): on-screen pink only in debug.
            "code_visible_onscreen": cfg!(debug_assertions),
        })
    }

    fn caps(&self) -> FacetCaps {
        FacetCaps::NONE.themeable().resizable()
    }

    fn kind(&self) -> &'static str {
        "errors"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use facett::core::harness;

    #[test]
    fn errcode_tab_surfaces_codes_in_state_json() {
        let mut tab = ErrCodeTab::new(TITLE);
        // Renders headless without panic.
        let r = harness::headless_render(&mut tab);
        assert!(r.drew(), "the errcode tab drew");

        let s = tab.state_json();
        assert_eq!(s["scheme"], "facet-<component>-<n>");
        // The build stamp / git hash is present.
        assert!(s["git_hash"].as_str().is_some());
        assert!(s["version"].as_str().unwrap().contains("facett-demo v"));
        // The whole catalog is exposed as data.
        assert!(s["code_count"].as_u64().unwrap() >= 20, "the matrix carries the whole registry");
        assert_eq!(
            s["matrix"].as_array().unwrap().len(),
            errcode::registry().len(),
            "one matrix row per registry code"
        );
        // The triggered examples surface their CODE (not just a string).
        let raised = s["raised"].as_array().unwrap();
        assert!(!raised.is_empty(), "example errors were triggered");
        assert_eq!(raised[0]["code"], "facet-demo-1");
        assert_eq!(raised[0]["severity"], "error");
        // The consumer mount-prefix demonstration.
        assert_eq!(s["mount_example"]["cases"], "korp/cases:facet-demo-1");
        assert_eq!(s["mount_example"]["analysis"], "korp/analysis:facet-demo-1");
        // The tab itself is NOT in a failure state (catalog display, Info floor).
        assert_eq!(tab.severity(), facett::core::Severity::Info);
    }

    #[test]
    fn every_matrix_message_avoids_the_robot_error_markers() {
        // The matrix slide renders every canonical message as a label; none may
        // contain a robot-UI ERROR_MARKER stem or the deck error-atom net goes RED.
        const MARKERS: &[&str] = &["Failed to load", "is not served", "unavailable", "RPC: status:", "panicked"];
        for e in errcode::registry() {
            for m in MARKERS {
                assert!(
                    !e.message.contains(m),
                    "registry message {:?} contains the reserved marker {:?}",
                    e.message,
                    m
                );
            }
        }
    }
}