facett-about — the shared About-card widget
One-line pitch
One robot-testable egui Window that renders a nornir_appshell::Identity, so every suite app's About surface is ONE widget — not a per-app twin.
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); …; if button("Close") )) + the identical dismissed = !window_open || close_clicked teardown — differing only in data:
| korp | holger | |
| logo width | 560 |
360 (LFS-tolerant Option) |
| version line | — | v{VERSION} monospace-strong |
| extra lines | one italic raven flavour line | FRAMEWORK[] bullet list |
That is a data-parameterised widget masquerading as two copies. facett-about owns the one body; the Identity (edda leaf nornir-appshell) carries the data. Each app KEEPS only let id = Identity{..} + a toggle bool + the draw_about_window(..) call — the const-blobs and the egui body delete (~120 LOC of shell collapses; +dwarves, nornir, ordning get a shared home instead of a 3rd/4th/5th copy).
Design — the chosen shape
Consumes nornir_appshell::Identity (the pure, egui-FREE model already landed in edda@773d977). Deps: egui, image (logo decode), nornir-appshell, serde_json. NO facett-core / nornir / domain edge — a leaf beside facett-card / facett-menu / facett-jobview.
The reproduction map (no visible change)
Every Identity field paints where korp/holger painted it — extra sections are gated on non-empty, so korp shows no framework header and holger shows no subtitle:
about-card render (draw_about_window)
├─ logo? Image.from_texture(logo).max_width(logo_max_width).alt_text(logo_alt) [Option → text fallback]
├─ heading display_name
├─ version? "v{version}" monospace strong (skipped when blank → korp)
├─ tagline
├─ subtitle? label (korp SUBTITLE; blank → holger)
├─ engines? italic-weak flavour lines (korp's raven line)
├─ framework? "Built on the shared nordisk framework:" + "· {line}" small (holger FRAMEWORK[])
├─ links? hyperlink_to(label, url) (nornir endpoint / docs)
└─ Close button → close_clicked
teardown: dismissed = !window_open || close_clicked; if dismissed { *open = false }
API
logo_texture(ctx, &Identity) -> Option<TextureHandle>— LFS-tolerant PNG→texture (Nonewhen absent / empty / an un-pulled git-lfs pointer — never a panic; mirrors holger's gracefulimage::load_from_memory(..).ok()).draw_about_window(ctx, &mut open, &Identity, Option<&TextureHandle>, logo_max_width) -> bool— the verbatim korp/holger body. Returnstrueiff a dismiss happened this frame (korp/holger's exact contract, so their call sites port unchanged).AboutCard::new(ctx, &id).with_max_width(360.0)then.show(ctx, &mut open, &id) -> bool— the ergonomic form: decodes + caches the logo once;showreturns whether the window is still open afterwards.state_json(&Identity, open) -> Value— thin passthrough toIdentity::state_json(the oracle a robot asserts), so a caller drives the whole About surface throughfacett_about::alone.DEFAULT_LOGO_MAX_WIDTH = 560.0(korp's).
Robot test (L4) — matrix cell facett-about/window
tests/robot.rs renders the REAL draw_about_window under egui_kittest with a sample Identity and asserts off the live AccessKit tree: the logo alt-text, the wordmark heading, the v{version} line, and a framework bullet all paint; then a real Close click dismisses (open → false, dismiss flag true). A second case proves a closed card paints nothing. Inline src/lib.rs tests cover the LFS-tolerant decode (real PNG → Some, LFS pointer / none / empty → None) and the state_json passthrough. RED-when-broken (verified by neutralizing): breaking the logo alt-text binding fails about_window_paints_identity_and_close_dismisses with "the logo painted with its alt-text". Emits gated #[cfg(feature="testmatrix")].
Idiot guide (drop it in)
use facett_about::{AboutCard, Identity};
// once (or when identity changes):
let id = Identity { logo_png: Some(include_bytes!("../assets/korp_logo.png").to_vec()),
logo_alt: "Korp logo — Hugin & Munin ravens".into(),
..Identity::new("korp", "Korp", env!("CARGO_PKG_VERSION"),
"Hugin watches the now — Munin keeps the history.") };
let card = AboutCard::new(ctx, &id); // holger: .with_max_width(360.0)
// each frame:
card.show(ctx, &mut self.about_open, &id); // window ✕ or Close set about_open = false
Must-NOT-change invariant
The About window must not visibly change when korp/holger repoint onto this widget: anchor CENTER_CENTER, per-app logo_max_width, and the label order above are the contract the per-app robot snapshots gate on. The logo path stays Option / text-fallback — never panic on an un-pulled LFS blob.
Status / gap
Widget + robot test shipped; the per-app repoints (korp/holger consume + delete their about.rs egui body; dwarves/nornir/ordning new consumers) are DEFERRED to the per-app agents (see the app-shell-leaf plan §3).