facett-demo/src/about_tab.rs
//! **The `About` tab** — the shared **About-card widget** (`facett-about`) made
//! visible in the gallery.
//!
//! `facett-about` is deliberately NOT a `Facet`: it is the ONE robot-testable
//! identity widget every suite app (korp · holger · dwarves · nornir) mounts as a
//! modal window over a [`nornir_appshell::Identity`](facett_about::Identity). So —
//! exactly like `errcode_tab` wraps the error-code registry — this demo-local
//! wrapper gives it a deck tab: the pane renders the card's field map **inline**
//! (wordmark · version · tagline · subtitle · engines · framework list · the
//! MEASURED GPU rows via the shared [`facett_about::gpu_rows`] — the ONE writer,
//! never a re-derivation) over a seeded nordisk [`Identity`], and a button opens
//! the **real** shared modal ([`facett_about::draw_about_window`]) so the actual
//! widget every app ships is on screen, not a look-alike.
//!
//! `state_json` delegates to [`facett_about::state_json`] — the same oracle a
//! suite app's robot asserts — so the tab's observable surface IS the widget's.
use facett::Facet;
use facett::core::FacetCaps;
use facett_about::{GpuStatus, Identity};
/// The stable tab title (the deck / discovery-contract key + the LEGEND key).
pub const TITLE: &str = "About";
/// The seeded demo [`Identity`] — facett's own card, every field populated so the
/// whole reproduction map paints (korp's `engines` flavour line AND holger's
/// `framework` bullets on one card). No logo bytes: the card's LFS-tolerant path
/// (`logo_png: None` → text identity, never a panic) is the one exercised.
pub fn demo_identity() -> Identity {
Identity {
subtitle: "every facett component on one deck, fake-but-realistic data".to_string(),
repository: "ssh://git@git.gunnar.rs:2222/nordisk/facett.git".to_string(),
links: vec![(
"git.gunnar.rs/nordisk/facett".to_string(),
"https://git.gunnar.rs/nordisk/facett".to_string(),
)],
framework: vec![
"facett-core — the Facet contract + the L0 render kernel".to_string(),
"facett-app — NavShell · Router · scene".to_string(),
"facett-about — this very card (one widget, no per-app twin)".to_string(),
],
engines: vec!["the ravens carry the data — hugin ute, munin hemma".to_string()],
..Identity::new(
"facett-demo",
"facett",
env!("CARGO_PKG_VERSION"),
"the component constellation — ONE showcase, every Facet, all palettes",
)
}
}
/// The About tab: the shared card's body inline + the real modal on demand.
pub struct AboutTab {
title: &'static str,
id: Identity,
/// The shared modal's toggle (the same bool every suite app keeps).
window_open: bool,
/// How many times the modal was dismissed — observable proof the REAL shared
/// window (not just the inline body) ran its dismiss contract.
dismissed: u32,
}
impl AboutTab {
/// The populated gallery tab over [`demo_identity`].
pub fn new(title: &'static str) -> Self {
Self { title, id: demo_identity(), window_open: false, dismissed: 0 }
}
/// An **empty-identity** twin (everything blank) — the mega-matrix's `Empty`
/// data-state cell: the card must render its skips (no version line, no
/// framework list, no links) rather than a panic or placeholder junk.
pub fn empty(title: &'static str) -> Self {
Self { title, id: Identity::new("", "", "", ""), window_open: false, dismissed: 0 }
}
}
impl Facet for AboutTab {
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("the shared About card (facett-about)");
ui.label(
"ONE widget renders every suite app's About surface from a nornir \
Identity — korp · holger · dwarves · nornir mount this exact card. \
Below: the card's field map inline; the button opens the REAL modal.",
);
ui.separator();
// ── The card body, inline — the reproduction map in identity order. ──
ui.vertical_centered(|ui| {
ui.heading(self.id.display_name.as_str());
if !self.id.version.is_empty() {
ui.label(egui::RichText::new(format!("v{}", self.id.version)).monospace().strong());
}
if !self.id.tagline.is_empty() {
ui.label(self.id.tagline.as_str());
}
if !self.id.subtitle.is_empty() {
ui.label(self.id.subtitle.as_str());
}
if !self.id.engines.is_empty() {
ui.add_space(6.0);
for line in &self.id.engines {
ui.label(egui::RichText::new(line).italics().weak());
}
}
if !self.id.framework.is_empty() {
ui.add_space(8.0);
ui.label(egui::RichText::new("Built on the shared nordisk framework:").weak());
for line in &self.id.framework {
ui.label(egui::RichText::new(format!("· {line}")).small());
}
}
// The MEASURED GPU rows — the shared writer (`facett_about::gpu_rows`),
// so this pane can never disagree with what the suite apps' cards say.
ui.add_space(8.0);
ui.separator();
facett_about::gpu_rows(ui, &GpuStatus::probe());
if !self.id.links.is_empty() {
ui.add_space(6.0);
for (label, url) in &self.id.links {
ui.hyperlink_to(label.as_str(), url.as_str());
}
}
});
ui.separator();
// ── The REAL shared modal, on demand — the widget the apps actually ship.
if ui.button("Open the About window").clicked() {
self.window_open = true;
}
});
// Drawn outside the scroll area: an egui `Window` floats over the pane
// regardless, and the card anchors itself CENTER_CENTER. No logo texture
// (`None`) — the LFS-tolerant text-identity path.
if self.window_open {
let was_open = self.window_open;
let dismissed = facett_about::draw_about_window(
ui.ctx(),
&mut self.window_open,
&self.id,
None,
360.0,
);
if was_open && dismissed {
self.dismissed += 1;
}
}
}
fn state_json(&self) -> serde_json::Value {
// The widget's own oracle (identity + the "gpu" fold), plus the tab's
// modal-interaction counters.
let mut v = facett_about::state_json(&self.id, self.window_open);
if let Some(o) = v.as_object_mut() {
o.insert("dismissed".to_string(), serde_json::json!(self.dismissed));
}
v
}
fn update_json(&mut self, msg_json: &str) {
// The robot seam: {"open": true/false} drives the modal toggle.
if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json)
&& let Some(b) = v.get("open").and_then(|x| x.as_bool())
{
self.window_open = b;
}
}
fn caps(&self) -> FacetCaps {
FacetCaps::NONE.themeable()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_seeded_identity_populates_every_card_section() {
let t = AboutTab::new(TITLE);
let s = t.state_json();
assert_eq!(s["wordmark"], "facett");
assert!(!s["version"].as_str().unwrap().is_empty(), "the version line paints");
assert!(s["framework"].as_array().unwrap().len() >= 3, "the framework bullets paint");
assert_eq!(s["links"].as_array().unwrap().len(), 1, "the source link paints");
assert_eq!(s["open"], false, "the modal starts closed");
assert_eq!(t.title(), TITLE, "the pane registers under the LEGEND key");
}
#[test]
fn the_empty_identity_renders_its_skips_as_data() {
let t = AboutTab::empty(TITLE);
let s = t.state_json();
assert_eq!(s["framework"].as_array().unwrap().len(), 0);
assert_eq!(s["links"].as_array().unwrap().len(), 0);
assert_eq!(s["has_logo"], false);
}
#[test]
fn the_robot_seam_opens_the_real_modal() {
let mut t = AboutTab::new(TITLE);
t.update_json(r#"{"open": true}"#);
assert_eq!(t.state_json()["open"], true, "update_json drives the modal toggle");
}
}