Three things were wrong with the host-card OS icons.
The Windows mark was Font Awesome 5's, which is still the Windows 8/10 flag
with the perspective skew — dated next to the flat four-pane mark Microsoft
has shipped since Windows 11. No icon set has the current one (Simple Icons
carries no windows/microsoft slug at all), so it is drawn here: four equal
squares at the authentic 11.377 + 1.246 proportion. The Decky plugin was
pulling FaWindows straight from react-icons, so it now inlines the masters
like the web console does, or it would have kept the old flag regardless.
Bazzite, CachyOS and Nobara collapsed onto their family's mark. The host
already advertises the full chain, so this is purely missing art: all three
now ship a leaf mark, because "a Bazzite box" and "a Fedora box" are
different machines to the person reading the card. CachyOS and Nobara come
from Simple Icons; Bazzite has no icon anywhere, so its "b" is lifted out of
the project's own badge (Apache-2.0, attributed).
On Android every non-square mark was stretched. A VectorPainter maps the
viewport onto the ImageVector's default size with independent x and y
scales, so declaring a 448x512 Tux as 24x24 dp squashed it — silently, no
crash, no warning. The longest viewport edge now sets the 24 dp box and the
other follows the ratio, which is what Icon()'s ContentScale.Fit expects.
A unit test pins the invariant; every other client was already correct.
Also: scripts/gen-os-icons.sh replaces the undocumented hand-run pipeline
that turns a master into the GTK symbolic SVG, the Windows PNG and the Apple
template PDF. It reproduces the committed artifacts byte-identically.
Verified: web and Decky typecheck, Decky bundles, Android compiles and its
tests pass, PunktfunkKit builds, osinfo's tests pass. Not verified on glass,
and the GTK/Windows client crates do not build on macOS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The client half of the host's new `os=` advert, shared once in pf-client-core:
`sanitize_os` (mDNS is unauthenticated input — lowercase `[a-z0-9._-]` tokens,
capped) and `os_icon_tokens`, the most-specific-first walk with the brand
aliases (`macos`→apple, `steamos`→steam) every platform resolves through.
`DiscoveredHost` carries the chain, `KnownHost` persists it (`serde(default)`,
elided when empty — older stores load unchanged and older clients read back
exactly what they wrote), `upsert` moves it only when carried, and `learn_os`
mirrors `learn_mac` — no-op, no disk write when unchanged — so the mark
survives the host going to sleep.
GTK shells: the card's status row leads with a recolorable symbolic glyph.
That needed real embedded assets — the shells had none — so `data/` gains the
ten `pf-os-*-symbolic` SVGs compiled into a gresource (new build.rs,
glib-build-tools) and registered on the icon theme at startup; the Adwaita
theme then tints them like every other status glyph.
WinUI shell: reactor renders raster-from-URI only, so the embedded mid-gray
PNGs (legible on both themes) materialize once into
%LOCALAPPDATA%\punktfunk\os-icons\ — the library's poster-art pattern — and
the tile's status row leads with a 16px image.
The couch UI plumbs `HostRow.os` (live advert preferred, else the store) for a
Skia glyph that is a declared follow-up; `--list-hosts` / `hosts --json` emit
the stored chain so the Decky plugin can read it. A host that advertises no
`os` renders everywhere exactly as it did before the field existed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>