diff --git a/docs/superpowers/specs/2026-10-04-app-icon-and-desktop-entry-design.md b/docs/superpowers/specs/2026-10-04-app-icon-and-desktop-entry-design.md index 047527b..5676086 100644 --- a/docs/superpowers/specs/2026-10-04-app-icon-and-desktop-entry-design.md +++ b/docs/superpowers/specs/2026-10-04-app-icon-and-desktop-entry-design.md @@ -189,15 +189,38 @@ passes; the existing `toolbar icons:` line already guards that class with cannot see it — and these SVGs have already been bitten once by librsvg, which rejects a file for a `--` inside an XML comment. -So the new assertion distinguishes *named* from *renderable*: +The two are therefore caught in two different places, because **no runtime GTK call can see the +malformed one.** This was measured rather than assumed, and three candidate checks were tried and +rejected: + +- `gtk_icon_theme_lookup_icon()` never returns `NULL` and its paintable's intrinsic size is the + size that was *asked for*, so it reads `128x128` for a valid icon, a malformed one and a name + that does not exist at all. The same three cases snapshot to an identical `GSK_TEXTURE_NODE` of + the requested bounds, since GTK substitutes `image-missing`. There is nothing to compare. +- `GMarkup` accepts `` without complaint. It is a deliberately lenient subset + parser, so it cannot stand in for the strict one that refuses the file. +- `gdk_pixbuf_new_from_file()` on an SVG goes through glycin on this system, which sandboxes a + subprocess per load, emits fontconfig errors, and refuses this project's existing icons outright + for reasons of its own. Not a usable oracle. + +So the malformed file is caught at **build time**, which is better than a probe anyway: every +`` line in `src/icons/udpscope.gresource.xml` gains `preprocess="xml-stripblanks"`, which +makes `glib-compile-resources` pipe each SVG through `xmllint`. A double hyphen inside a comment +then **fails the build** with the file, line and column, instead of drawing nothing at runtime. +The four existing icons already pass it. The cost is that the `xmllint` binary becomes a build +dependency; it ships with libxml2, which GTK already requires. + +The runtime line then asserts what a runtime check genuinely can: ``` app icons: org.udpscope.app(ok 128x128) org.udpscope.app-symbolic(ok 16x16) ``` -It looks each icon up and queries the resulting paintable's intrinsic size. **Both entries must read -`ok` with a non-zero size**; a parse failure reports `0x0` with the name still resolving, which is -precisely the case the existing check misses. +`ok` is `gtk_icon_theme_has_icon()` **and** the resource bytes being present under the registered +path, and the size is the one the file's root `` element declares, read out of those bytes. +**Both entries must read `ok` at those sizes.** That covers the unregistered-path case, a renamed +or mis-pathed file, and a canvas size that drifted off the HIG's 128 — and it is computed on the +first tick beside `toolbar icons:`, not in the dump, which runs after the window is destroyed. Alongside it: @@ -212,7 +235,9 @@ Alongside it: ## Documentation -- **CLAUDE.md**: the `app icons:` dump line as a documented assertion; the windows invariant revised +- **CLAUDE.md**: the `app icons:` dump line as a documented assertion, and the `xml-stripblanks` + build-time check beside the five existing icon-authoring rules — the sixth rule is that the + malformed-SVG failure is a *build* error now and no runtime check can replace it; the windows invariant revised to one `GtkWindow` and two `AdwDialog`s; the `desktop-entry` target and the build-path caveat; and the icon-authoring rules amended so "filled paths only" is scoped to symbolic icons. - **README.md**: the feature narrative — the icon's HIG constraints and the two omissions