facett-about/src/lib.rs
//! # facett-about — the shared **About card** widget
//!
//! The ONE robot-testable egui `Window` that renders a
//! [`nornir_appshell::Identity`], so every suite app's *About* surface (korp ·
//! holger · dwarves · nornir · ordning) is **one widget, not a per-app twin**
//! (LAW #5). It ports korp's original `draw_about_window` **verbatim**,
//! swapping the hard-coded `WORDMARK`/`TAGLINE`/`SUBTITLE`/`FRAMEWORK[]` consts
//! for [`Identity`] accessors, so korp's *and* holger's current layouts both
//! reproduce with **no visible change**.
//!
//! ## Why it exists (LAW #5 — DRY)
//! `korp/src/about.rs` and `holger/server/ui/src/about.rs` carried the SAME egui
//! body — `Window(..).collapsible(false).resizable(false).anchor(CENTER_CENTER)
//! .open(..).show( vertical_centered( Image.max_width(..).alt_text(..);
//! heading(wordmark); ...; Close ))` + the identical dismiss teardown — differing
//! only in DATA: korp `max_width = 560` + an italic raven flavour line; holger
//! `max_width = 360` + a `v{version}` line + the `FRAMEWORK[]` bullet list. That
//! is a data-parameterised widget masquerading as two copies. This leaf owns the
//! one body; the [`Identity`] carries the data.
//!
//! ## The reproduction map (no visible change)
//! Every field of [`Identity`] paints where the two apps painted it:
//! | Identity field | painted as | korp | holger |
//! |---|---|---|---|
//! | `logo_png` (→ [`logo_texture`]) | `Image.max_width(logo_max_width).alt_text(logo_alt)` | ✓ (560) | ✓ (360, LFS-tolerant `Option`) |
//! | `display_name` | `heading` | ✓ | ✓ |
//! | `version` (if non-empty) | `v{version}` monospace-strong | — | ✓ |
//! | `server_version` (if non-empty) | `server v{server_version}` monospace-weak | ✓ (connected korp-server / embedded engine) | — |
//! | `tagline` | `label` | ✓ | ✓ |
//! | `subtitle` (if non-empty) | `label` | ✓ (SUBTITLE) | — |
//! | `engines` (if non-empty) | italic-weak flavour lines | ✓ (the raven line) | — |
//! | `framework` (if non-empty) | "Built on …" weak header + `· line` small | — | ✓ (FRAMEWORK[]) |
//! | `links` (if non-empty) | `hyperlink_to(label, url)` | — | — (nornir/dwarves) |
//!
//! ## The GPU rows (every app, no per-app wiring)
//!
//! Below the identity, the card paints **what the app is actually rendering on** —
//! adapter name, `backend · device class · N adapter(s) enumerated`, and an honest
//! *GPU lane* line. Two facts made this necessary, and neither was visible from any
//! UI in the suite:
//!
//! * a host can silently be on **llvmpipe** — a CPU software rasteriser that is
//! itself a Vulkan ICD, so "Vulkan" proves nothing (LAW #4 cares about the *lane*,
//! not the API string). The row says `SOFTWARE (CPU rasteriser)`, in the error
//! colour, and it says it even when the adapter reports itself `DiscreteGpu`,
//! because the class comes from the selection's software flag, never from
//! `device_type` alone;
//! * a GPU lane can be **installed and never called** — `renderer_installed()` is
//! true, and every frame still goes through `painter.circle_filled`.
//!
//! The lane line is therefore MEASURED, from `facett_core::render::lane`'s frame
//! counters, and reports `unknown` rather than guessing when an installed lane does
//! not count its frames. The whole story is folded into [`state_json`] under `"gpu"`
//! so a robot asserts it. Rows come from `GpuStatus::rows()` — the ONE writer the UI
//! and the assertion share.
//!
//! korp maps its one italic raven line onto `engines`; holger maps `FRAMEWORK[]`
//! onto `framework`. Each app KEEPS only `let id = Identity{..}` + the toggle bool
//! + the `draw_about_window(..)` call — the const-blobs and the egui body delete.
//!
//! ## The API
//! * [`logo_texture`] — LFS-tolerant PNG→texture (holger's `Option`; `None` when
//! the bytes are absent, empty, or an un-pulled git-lfs pointer — never a panic).
//! * [`draw_about_window`] — the verbatim korp/holger body, parameterised by
//! `Identity`, `Option<&TextureHandle>`, and `logo_max_width`. Returns
//! **`true` iff a dismiss happened this frame** (korp/holger's exact contract).
//! * [`AboutCard`] — the ergonomic form: caches the decoded logo + `logo_max_width`
//! once, then `AboutCard::show(ctx, &mut open, &id)` renders each frame.
//! * [`gpu_rows`] — the GPU rows on their own, so a diagnostics pane or status bar
//! can mount the same four lines instead of re-deriving them.
//! * [`state_json`] — [`Identity::state_json`] plus a `"gpu"` fold (adapter · backend
//! · device class · adapter count · software · lane · lane_frames · lanes · rows),
//! so a caller need not reach into `nornir_appshell` or `facett_core` for it.
//! * `*_with_gpu` siblings ([`draw_about_window_with_gpu`], [`state_json_with_gpu`],
//! [`AboutCard::with_gpu`]) take an explicit [`GpuStatus`] instead of the live
//! process globals — additive, so every existing call site is unchanged and gets
//! the live probe.
use egui::{Context, TextureHandle};
pub use nornir_appshell::Identity;
/// The GPU facts the card paints — re-exported so a caller drives the whole About
/// surface through `facett_about::` alone (the [`state_json`] precedent).
pub use facett_core::render::lane::{GpuLaneUse, GpuStatus};
/// korp's original About logo width — the default when a caller does not override
/// it. holger passes `360.0`.
pub const DEFAULT_LOGO_MAX_WIDTH: f32 = 560.0;
/// Decode an [`Identity`]'s embedded logo PNG into an egui texture, **LFS-tolerant**.
///
/// Returns `None` — never a panic — when the identity has no logo, the bytes are
/// empty, or the bytes don't decode (a checkout where the git-lfs blob was not
/// pulled: the compiled-in bytes are the pointer text, not a PNG). The card then
/// shows its text identity without the hero image. Mirrors holger's original
/// graceful `image::load_from_memory(..).ok()`.
pub fn logo_texture(ctx: &Context, id: &Identity) -> Option<TextureHandle> {
let bytes = id.logo_png.as_ref()?;
if bytes.is_empty() {
return None;
}
let img = image::load_from_memory(bytes).ok()?.to_rgba8();
let (w, h) = img.dimensions();
let color = egui::ColorImage::from_rgba_unmultiplied([w as usize, h as usize], img.as_raw());
Some(ctx.load_texture(
format!("facett_about_logo_{}", id.name),
color,
egui::TextureOptions::LINEAR,
))
}
/// The headless **oracle** for the About card: [`Identity::state_json`] (wordmark ·
/// version · logo_alt · has_logo · framework · …) **plus a `"gpu"` fold** — the
/// adapter, its backend and device class, how many adapters were enumerated, the
/// software flag, the measured lane use, and the painted rows verbatim.
///
/// `open` reports whether the window is showing. The identity keys are untouched, so
/// an existing About robot keeps passing; the GPU half is what lets one assert "this
/// app is on the 4090 and its map lane is really painting" without pixels. The GPU
/// facts are read from the process globals a wgpu host records at startup — use
/// [`state_json_with_gpu`] to assert a specific status.
pub fn state_json(id: &Identity, open: bool) -> serde_json::Value {
state_json_with_gpu(id, open, &GpuStatus::probe())
}
/// [`state_json`] against an explicit [`GpuStatus`] — the form a test uses, so the
/// oracle can be asserted for a 4090, for llvmpipe and for a GPU-less process inside
/// one test binary (the live probe reads process globals and is therefore whatever
/// this process happens to have brought up).
pub fn state_json_with_gpu(id: &Identity, open: bool, gpu: &GpuStatus) -> serde_json::Value {
let mut js = id.state_json(open);
if let Some(obj) = js.as_object_mut() {
obj.insert("gpu".to_owned(), gpu.to_json());
}
js
}
/// Paint the **GPU rows** — the answer to "what is this app actually rendering on":
/// adapter name, backend · device class · adapters enumerated, the honest lane line,
/// **why an idle lane is idle**, and a recorded bring-up failure's code + remedy when
/// there was one.
///
/// Public because the About card is not the only place worth telling: a diagnostics
/// pane or status bar mounts the SAME rows rather than re-deriving them (LAW #5). The
/// row STRINGS come from [`GpuStatus::rows`], the one writer a robot also reads, so
/// the assertion and the pixels cannot drift.
///
/// Colour carries the warning a sentence cannot: a software rasteriser is painted in
/// the error colour (a 4090 box quietly on llvmpipe is the whole point of these rows),
/// an installed-but-unused or unknown lane in the warn colour.
pub fn gpu_rows(ui: &mut egui::Ui, gpu: &GpuStatus) {
let (warn, err, weak, normal) = {
let v = ui.visuals();
(v.warn_fg_color, v.error_fg_color, v.weak_text_color(), v.text_color())
};
ui.label(
egui::RichText::new(gpu.adapter_line())
.monospace()
.color(if gpu.is_software() { err } else { normal }),
);
ui.label(egui::RichText::new(gpu.detail_line()).monospace().small().color(
if gpu.is_software() { err } else { weak },
));
ui.label(egui::RichText::new(gpu.lane_line()).monospace().small().color(match gpu.lane {
GpuLaneUse::InUse { .. } => weak,
GpuLaneUse::NotInstalled => weak,
GpuLaneUse::InstalledUnused | GpuLaneUse::Unknown { .. } => warn,
}));
// WHY an idle lane is idle. Without this the previous row is ambiguous by
// construction: "installed but UNUSED … drew 0 frames" is equally true of a lane that
// correctly stood aside and of one that is broken. MEASURED 2026-08-02 — korp printed
// exactly that on an RTX 4090 with nothing wrong (the map pane had not been painted:
// modal chooser, landing tab is Tables), and it read as a fault to both the owner and an
// agent with tools. The reason reached `state_json` first and this row did not exist,
// which is the same defect one layer out: the robot could see it and the human could
// not.
if let Some(line) = gpu.idle_reason_line() {
ui.label(egui::RichText::new(line).monospace().small().color(weak));
}
if let Some(line) = gpu.unavailable_line() {
ui.label(egui::RichText::new(line).monospace().small().color(err));
}
}
/// Draw the **About** modal window into `ctx` — the ONE render body korp and holger
/// (and the robot test) share.
///
/// * `open` gates visibility; it is set to `false` when the user dismisses the
/// window (its own ✕ or the Close button).
/// * `id` is the app identity ([`Identity`]) — every field paints per the crate-doc
/// reproduction map.
/// * `logo` is the decoded hero texture (from [`logo_texture`]); shown with the
/// stable `id.logo_alt` accessible name so the logo is observable to a robot.
/// `None` (an un-pulled LFS blob, or an app with no logo) draws the text identity
/// without the hero image — the exact holger behaviour.
/// * `logo_max_width` caps the logo width (korp `560.0`, holger `360.0`).
///
/// Returns **`true` iff a dismiss happened this frame** (korp/holger's exact
/// contract, so their call sites port unchanged). Draws nothing and returns
/// `false` when `*open` is already `false`.
pub fn draw_about_window(
ctx: &Context,
open: &mut bool,
id: &Identity,
logo: Option<&TextureHandle>,
logo_max_width: f32,
) -> bool {
draw_about_window_with_gpu(ctx, open, id, logo, logo_max_width, &GpuStatus::probe())
}
/// [`draw_about_window`] against an explicit [`GpuStatus`] instead of the live
/// process globals — the form a test drives (and a host that already holds its own
/// decision). Additive: every existing call site keeps the five-argument form and
/// gets the live probe.
pub fn draw_about_window_with_gpu(
ctx: &Context,
open: &mut bool,
id: &Identity,
logo: Option<&TextureHandle>,
logo_max_width: f32,
gpu: &GpuStatus,
) -> bool {
if !*open {
return false;
}
let mut window_open = true;
let mut close_clicked = false;
egui::Window::new(format!("About {}", id.display_name))
.collapsible(false)
.resizable(false)
.anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
.open(&mut window_open)
.show(ctx, |ui| {
ui.vertical_centered(|ui| {
if let Some(logo) = logo {
ui.add(
egui::Image::from_texture(logo)
.max_width(logo_max_width)
.alt_text(id.logo_alt.clone()),
);
}
ui.add_space(10.0);
ui.heading(id.display_name.as_str());
// holger's `v{VERSION}` monospace line (skipped when blank — korp).
// This is the UI / crate version.
if !id.version.is_empty() {
ui.label(
egui::RichText::new(format!("v{}", id.version))
.monospace()
.strong(),
);
}
// The connected SERVER / engine version — a weak sibling of the UI
// version line (korp thin: korp-server's `Health` version; korp fat:
// the embedded engine crate version). Skipped when blank/unknown, so
// the card shows only `v{version}` until the backend version is known.
if !id.server_version.is_empty() {
ui.label(
egui::RichText::new(format!("server v{}", id.server_version))
.monospace()
.weak(),
);
}
if !id.tagline.is_empty() {
ui.label(id.tagline.as_str());
}
// korp's SUBTITLE (holger has none → blank → skipped).
if !id.subtitle.is_empty() {
ui.label(id.subtitle.as_str());
}
// korp's italic raven flavour line(s) — mapped onto `engines`.
if !id.engines.is_empty() {
ui.add_space(6.0);
for line in &id.engines {
ui.label(egui::RichText::new(line).italics().weak());
}
}
// holger's FRAMEWORK[] — the shared-framework bullet list.
if !id.framework.is_empty() {
ui.add_space(8.0);
ui.label(
egui::RichText::new("Built on the shared nordisk framework:").weak(),
);
for line in &id.framework {
ui.label(egui::RichText::new(format!("· {line}")).small());
}
}
// WHAT THIS APP IS ACTUALLY RENDERING ON. Not decoration: the two
// facts these rows carry were both invisible from every UI in the
// suite — that a host had silently fallen back to llvmpipe, and that
// a GPU lane can be installed and never called. Painted for every
// app, from the process globals, with no per-app wiring.
ui.add_space(8.0);
ui.separator();
gpu_rows(ui, gpu);
// Extra (label, url) links (nornir's endpoint/workspace, a docs
// pointer, …) — none for korp/holger, so the list is empty there.
if !id.links.is_empty() {
ui.add_space(6.0);
for (label, url) in &id.links {
ui.hyperlink_to(label.as_str(), url.as_str());
}
}
ui.add_space(10.0);
if ui.button("Close").clicked() {
close_clicked = true;
}
});
});
// The window's own ✕ flips `window_open`; the Close button flips `close_clicked`.
let dismissed = !window_open || close_clicked;
if dismissed {
*open = false;
}
dismissed
}
/// The **ergonomic** About card: decodes the logo once (holger's LFS-tolerant
/// `Option`) and remembers `logo_max_width`, so the per-frame call is just
/// [`AboutCard::show`]`(ctx, &mut open, &id)` — the app keeps only a toggle bool.
///
/// Build it once (or when the identity changes) and store it in the app; call
/// [`show`](AboutCard::show) each frame with the same [`Identity`].
pub struct AboutCard {
/// The decoded hero logo, if any (`None` = no logo / un-pulled LFS blob).
logo: Option<TextureHandle>,
/// The logo width cap (korp `560.0`, holger `360.0`).
logo_max_width: f32,
/// A pinned GPU status, or `None` to read the live process globals each show —
/// which is what an app wants, since a lane's frame count changes as it paints.
gpu: Option<GpuStatus>,
}
impl AboutCard {
/// Decode `id`'s logo (LFS-tolerant) and build a card with korp's default
/// [`DEFAULT_LOGO_MAX_WIDTH`]. Chain [`with_max_width`](Self::with_max_width)
/// for holger's narrower `360.0`.
pub fn new(ctx: &Context, id: &Identity) -> Self {
AboutCard {
logo: logo_texture(ctx, id),
logo_max_width: DEFAULT_LOGO_MAX_WIDTH,
gpu: None,
}
}
/// Override the logo width cap (holger passes `360.0`).
pub fn with_max_width(mut self, logo_max_width: f32) -> Self {
self.logo_max_width = logo_max_width;
self
}
/// Pin the [`GpuStatus`] the card reports instead of reading the live process
/// globals each frame (a test, or a host that resolved the adapter itself).
pub fn with_gpu(mut self, gpu: GpuStatus) -> Self {
self.gpu = Some(gpu);
self
}
/// `true` iff a logo texture decoded (so the card shows the hero image, not the
/// text-only fallback) — the robot-observable twin of `Identity::has_logo`
/// after decode.
pub fn has_logo_texture(&self) -> bool {
self.logo.is_some()
}
/// Render the About window for `id` this frame. Returns **whether the window is
/// still open afterwards** (`true` = still showing; `false` = dismissed this
/// frame or already closed) — the ergonomic inverse of
/// [`draw_about_window`]'s dismiss flag, matching a `while card.show(..)` toggle.
pub fn show(&self, ctx: &Context, open: &mut bool, id: &Identity) -> bool {
let gpu = match &self.gpu {
Some(g) => g.clone(),
None => GpuStatus::probe(),
};
draw_about_window_with_gpu(ctx, open, id, self.logo.as_ref(), self.logo_max_width, &gpu);
*open
}
}
// ── testmatrix emit shim (real row emitter with `--features testmatrix`, a
// compiled-out no-op otherwise — the facett-confirm idiom) ─────────────────
/// Emit one functional-status row (no-op unless built `--features testmatrix`).
#[cfg(feature = "testmatrix")]
pub fn emit(component: &str, check: &str, ok: bool, detail: &str) {
nornir_testmatrix::functional_status(component, check, ok, detail);
}
/// No-op shape compiled when `testmatrix` is OFF (the release path).
#[cfg(not(feature = "testmatrix"))]
#[inline(always)]
pub fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}
#[cfg(test)]
mod tests {
use super::*;
/// A tiny **real** 2×2 PNG, encoded at runtime so the decode path is exercised
/// with genuine bytes (no committed asset needed).
fn tiny_png() -> Vec<u8> {
let img = image::RgbaImage::from_pixel(2, 2, image::Rgba([200, 30, 30, 255]));
let mut buf = std::io::Cursor::new(Vec::new());
image::DynamicImage::ImageRgba8(img)
.write_to(&mut buf, image::ImageFormat::Png)
.expect("encode tiny PNG");
buf.into_inner()
}
fn holgerish_identity(logo: Option<Vec<u8>>) -> Identity {
Identity {
logo_png: logo,
logo_alt: "test logo — a raven".into(),
framework: vec!["jera — the shared job manager".into()],
..Identity::new("testapp", "TestApp", "9.9.9", "a test tagline")
}
}
/// **RED-when-broken: the logo decode is LFS-tolerant.** Real PNG bytes decode
/// to `Some` texture; an un-pulled git-lfs pointer (present but not a PNG)
/// decodes to `None` without a panic; a `None`/empty logo is `None`. If the
/// decode started panicking on a pointer, or dropped a real PNG, this is red.
#[test]
fn logo_texture_is_lfs_tolerant() {
let ctx = egui::Context::default();
// Real PNG → Some texture.
let ok = holgerish_identity(Some(tiny_png()));
assert!(logo_texture(&ctx, &ok).is_some(), "a real PNG decodes to a texture");
// Un-pulled LFS pointer (present but not a PNG) → None, no panic.
let lfs = holgerish_identity(Some(
b"version https://git-lfs.github.com/spec/v1\noid sha256:deadbeef\n".to_vec(),
));
assert!(logo_texture(&ctx, &lfs).is_none(), "an LFS pointer degrades to None");
// No logo at all → None.
let bare = holgerish_identity(None);
assert!(logo_texture(&ctx, &bare).is_none(), "no logo → None");
// Empty bytes → None.
let empty = holgerish_identity(Some(Vec::new()));
assert!(logo_texture(&ctx, &empty).is_none(), "empty bytes → None");
emit(
"facett-about",
"logo_texture_lfs_tolerant",
logo_texture(&ctx, &ok).is_some()
&& logo_texture(&ctx, &lfs).is_none()
&& logo_texture(&ctx, &bare).is_none(),
"real=Some lfs=None none=None",
);
}
/// The `state_json` fold carries the identity oracle **unchanged** — the same
/// shape korp/holger's About robots read (`wordmark` · `version` · `logo_alt` ·
/// `has_logo` · `framework`) — and adds `"gpu"` beside it. ADDITIVE: every
/// identity key must still be byte-identical to `Identity::state_json`, or an
/// existing app's About robot breaks.
#[test]
fn state_json_folds_gpu_beside_an_unchanged_identity() {
let id = holgerish_identity(Some(tiny_png()));
let js = state_json(&id, true);
let base = id.state_json(true);
for (k, v) in base.as_object().expect("identity object") {
assert_eq!(js.get(k), Some(v), "identity key {k} must be unchanged by the gpu fold");
}
assert_eq!(js["wordmark"], "TestApp");
assert_eq!(js["version"], "9.9.9");
assert_eq!(js["has_logo"], serde_json::json!(true));
assert!(!js["framework"].as_array().unwrap().is_empty());
// …and exactly one new key.
assert_eq!(
js.as_object().expect("object").len(),
base.as_object().expect("object").len() + 1,
"the fold adds `gpu` and nothing else"
);
assert!(js["gpu"].is_object(), "the gpu fold is present: {}", js["gpu"]);
// The keys a robot keys off must always exist, whatever this process has.
for k in ["adapter", "adapter_count", "device_class", "software", "lane", "lane_frames", "rows"] {
assert!(!js["gpu"][k].is_null() || k == "adapter", "gpu.{k} must be present: {}", js["gpu"]);
}
}
}