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

nordisk/facett

facett-about/.nornir/facett-about.md

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

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).