The MSIX package carried a single Square44x44Logo.png, so every taskbar and
Start slot on a HiDPI Windows 11 machine was Windows upscaling a 44px bitmap,
and with no altform variants Windows plated the icon itself. The generator now
emits the qualified catalog Windows resolves by itself: <Logo>.scale-{125,150,
200,400} for all four manifest logos at Microsoft's pixel sizes, and
Square44x44Logo.targetsize-{16..256} in the three required theme forms —
unqualified and altform-lightunplated carry the light tile, altform-unplated
(dark theme) the dark tile, the same split macOS dark mode gets. before-build
stages the whole assets/appx directory; app-builder-lib runs makepri and
`makeappx /l` as soon as it sees a qualified asset, so nothing else changes.
The hand-listed *-dark.png logos, generated but never staged, are gone.
Exposed along the way: the commit badge sat outside the portrait's silhouette
clip, and at 16–30px its anti-aliased edge stacked coverage on the plate's
corner pixels and changed the alpha channel between build flavors. The badge
now shares the clip, is plain black, and is left off direct renders below
32px where no SHA is legible anyway.
The desktop DMG's volume icon defaulted to the packager's icns, which is
actool's 256px fallback whenever mac.icon is the Icon Composer package, and
dmgbuild's badge option can only paste onto the stock removable-drive icon.
Ship our own drive-with-the-girl artwork instead: assets/dmg-volume.png
(hand-made, one artwork since volume icons have no appearance variants) is
emitted as apps/desktop/packaging/dmg-volume.icns, beside the DMG background
so the `files` whitelist keeps it out of the app bundle, and dmg.icon points
at it.
Tauri always uses the app icns as the volume icon and offers no override, so
the bootstrap workflow runs scripts/bundles/set_dmg_volume_icon.sh on the
built DMG before signing (the same .VolumeIcon.icns + custom-icon flag
dmgbuild sets), and the verify step checks the icon is on the mounted image.
Verified on macOS 26.6.2: both DMGs render the identical volume icon.
Tauri's bundler (cli 2.11.4) compiles an Icon Composer package listed in
bundle.icon into Assets.car with actool >= 26 and sets CFBundleIconName, so
the bootstrap installer gets an unbranded twin of the desktop package under
src-tauri/icons/icon.icon (generator target; the bundle stays the plain
brand because flavours only apply under apps/desktop/). Tauri only logs when
actool is too old, so the macOS job selects Xcode 26 and the verify step
asserts Assets.car and the plist key are in the built .app.
The desktop DMG's volume icon defaulted to the packager's icns, which is
actool's 256px fallback whenever mac.icon is the .icon package; point
dmg.icon at the full-resolution assets/icon.icns. Tauri's DMG already takes
--volicon from the bundle's .icns, which the regenerated outputs cover.
Next to macOS 26's own icons the flat plate + girl reads better than the
ringed tile, so the ring is off in every output: the tiles' inward stroke,
the layered icon's border layer and its targets. BORDER_ENABLED keeps the
ring code one flag away.
Without a ring the girl's lowest nodes are dragged past the tile edge
(EDGE_OVERSHOOT) so the outline crops her instead of leaving a gap.
Clear light/dark and Tinted light/dark now come from one grayscale mono
layer with Liquid Glass on: the parts of the girl that are black in light
mode become a near-black frosted ink (0.13 @ 90%), her white parts stay
white, each pixel painted exactly once so materials never stack. The art
layer hides under "tinted" and the mono layer shows only there.
Mac girl at 1.06x (between main's 1.12x and the layers' 1.0x), for the icns
and the layered icon alike. Chosen from a six-appearance matrix of real
IconServices renders next to Finder.
main scaled the girl 1.12x for every mac target (a white tile with a ring
reads small beside full-colour peers), but the macOS 26 layers were still
registered on the full-bleed squircle box, so macOS 26 would have shown a
smaller girl than macOS 15.
The layered icon's canvas is the plate itself, so derive its portrait box
from the mac box in plate coordinates. Ink overlap with the icns girl goes
from 0.65 to 0.996 IoU; only the two art layers change.
Clear light/dark and Tinted light/dark all render from one "tinted"
specialization: the system reads the layer as luminance (white = visible)
and tints it. Without one they fall back to the light layers, whose black
girl and ring have no luminance, so those four modes showed an empty plate.
Point "tinted" at the white layers already used for dark mode; the manifest
test now requires it and pins it to the dark image. Verified in the Dock on
macOS 26.6.2 in Clear and Tinted, light and dark: white girl and ring as
glass / in the tint colour.
Each layer carried both a fixed "image-name" and per-appearance
"image-name-specializations". actool treats the fixed name as the image for
every appearance and drops the specializations, so Assets.car held only the
light PNGs and dark mode showed the black girl and black ring on the dark
fill: a black squircle. Icon Composer itself writes one or the other, never
both.
Drop the fixed name; the manifest test now rejects it and requires a dark
specialization on every layer. Verified on macOS 26.6.2 in dark mode: the
catalog gains art-dark/border-dark and the Dock shows the white girl.
macOS 26 clips every app icon to its own squircle and shows legacy .icns art
inside a glass plate. Our border is a stroke on the tile's rounded rect, so on
26 the ring sits inside a grey rim and thins toward the corners where Apple's
curve and ours diverge.
Add an Icon Composer package (apps/desktop/assets/icon.icon) of canvas-filling
layers the system masks itself: a border band whose outer edge is the canvas
(the mask supplies the outline) and whose inner edge is Apple's mask offset
inward by the brand border thickness, so the band is the same width everywhere
along the visible outline; and the girl (+ commit badge) as a second layer.
Build flavours recolour the fill in icon.json only.
Apple's mask is a circular arc flanked by two cubic easing segments (the
Figma corner-smoothing construction). Fitted to macOS 26.6.2's own render of
a system icon on the 824-on-1024 grid: radius 214px, smoothing 0.645, rms
0.32px / worst 0.81px; a superellipse cannot get under 0.54px rms.
The .icns targets are unchanged: Apple's own 26-era .icns still sit on the
824 grid, and macOS <= 15 keeps showing that artwork.
The new contract test probes the border layer 640 points along the mask:
opaque at thickness-1.5px, transparent at thickness+1.5px. Rendering the
layers with the old r=185.4 corner violates it at 1448 probe points.
Linux uses apple-touch-icon.png as the window icon and Nix requires it to
match the full-bleed launcher icon, so keep it full-bleed and point the
dev-only app.dock.setIcon at assets/icon-mac.png instead.
Dev runs replace the Dock icon with public/apple-touch-icon.png, which the
generator rendered full-bleed, so it drew ~24% larger than its Dock
neighbors. Render it from the mac-grid master like the icns targets.
On the 824 grid the plate matches peers, but the girl inside a white tile
with a ring read small; scale her 1.12x about the plate center for every
mac target.
User installs failed with 'resvg-py is missing' because the web/desktop
source builds rendered icons on whatever python was on PATH. The default
brand outputs are now committed; source_build, apps/desktop build.mjs and
the npm/docusaurus pre-hooks consume them directly. Flavored release
bundles (canary/commit) still render into their own product dir.
icons-freshness-check now regenerates and fails on any byte diff.
The icon generator runs in an isolated icon-build environment (Nix, Docker,
PM) that has no application package, so importing the canonical regex from
hermes_cli.update_channel raised ModuleNotFoundError and broke the nix flake
check, the docker image build and the desktop pack. Keep a local copy and pin
it to the canonical rule with a behavioural test over every tag shape.
Five in-tree copies of the canary tag regex disagreed: darwin.py demanded 14 digits,
release_channels/r2 prune accepted any/8, the rest 8-or-14. r2.canary_doomed_keys
compared an 8-digit cutoff against the first 8 chars of a captured \d{8}, so a 14-digit
key matched on its date prefix by accident of regex greediness; it now slices the
suffix explicitly like scripts/release.py does. scripts/termux/deb_version.py keeps
its copy on purpose (runs under bare python3 in a workflow shell) and its test pins
it to the canonical one.
Canary uses yellow tiles. Commit builds use red tiles and the first seven
characters of HERMES_BUILD_COMMIT. Vector glyphs need no host fonts.
Only desktop targets use these colors. Shared branding remains unchanged.
PM-generated pixels preserve the artwork and native alpha masks. The macOS
content bounds remain (100, 100, 924, 924) on the 1024 canvas. Stable output
matches the pre-change baseline byte for byte.
Validation: 13 focused Python tests and 7 Node tests pass. Stable, canary,
and commit generation each wrote 35 targets and passed structural checks.
The full suite and native installation acceptance were not run.
Testers reported the macOS app icon reads oversized next to other apps:
the full-bleed 1024 squircle master put the shape ~149px past Apple's
icon grid on every side. macOS icns targets now render from an in-memory
mac master built on new squircle-mac-{light,dark} backgrounds — the same
white/#0d1117 squircle at 824x824 (r=185.4) centered in 1024 with 100px
margins, with the girl box scaled by 824/1024 to keep her relative size
inside the shape. All non-mac targets keep the full-bleed squircle.
Build TUI, web, desktop UI and runnable agent products from explicit
prepared inputs. Keep dependency preparation separate from distribution
packaging, with PM and native builds sharing uv environment construction.
Docker copies compiled frontend products instead of build dependencies.
Nix retains uv2nix environments and consumes shared assembly through store
references. Native desktop and Termux use the same launcher and frontend
contracts. Preserve the independent PM runtime and source imports from
arbitrary working directories.
Keep failed frontend builds from replacing the previous product, reject
source/output overlap, and bound dependency-process output draining.
Include hermes_wisdom in the Nix wheel: real CLI smoke tests exposed its
missing package declaration on the base revision too.
Verified focused Python and JavaScript suites, Docker build/runtime checks,
Nix desktop and CLI/ACP checks, standalone TUI and packaged Electron PTY,
and real full-Chromium interaction. Native signed installers, Android device
installation and the full repository suite remain CI verification.
Icon generation reported errors but returned success. Aggregate target
write and verification failures into exit 1, while processing later
targets. Keep rendering dependencies in the isolated build group.
A synchronous open error consumed a slot. Returning that slot alone
still let a deferred throw suppress an earlier callback and stall the
queue. Schedule queue draining outside completed callbacks and retain
the original exception.
Verified real clean-source generation and structural checks for all
35 targets, plus a real write failure through the Node/uv runner.
Native Windows child tests cover immediate and deferred open errors.
POSIX descriptor-limit cases are explicit skips here, not passes.
No signed package or native macOS acceptance is claimed.
Icon generation selected the application venv, where resvg-py was
missing. Adding it to the dev extra also selected it for production
payloads built with --all-extras.
Use a locked icon-build dependency group in an isolated uv environment.
Keep its wheel cache separate from the PM cache copied into payloads.
Route generation and structural checks through the same runner.
Verified clean-source generation without resvg in the runtime venv,
runtime dependency export exclusion, targeted Python and JS tests,
and the full web workspace build. Signed desktop packaging was not run.
- website wordmarks (logo.png/logo-dark.png) drop the black/white frame:
the girl alone on transparency, black for light navbar / white for dark
(docusaurus srcDark stays)
- BrandMark marks are now the app-icon squircle itself (transparent
corners, 824px safe-zone composition) instead of plain tiles; the
components no longer paint a tile/rounding
- generated icon outputs are NOT committed anymore: all 35 targets are
gitignored and regenerated on demand by the consuming pipelines via the
new scripts/generate-icons.mjs (website prebuild, desktop prebuild+dev,
installer prebuild, web prebuild)
- freshness lane switched from byte-compare to structural verification
(sizes, squircle corner transparency, ICO frame sets via header parse) —
no more windows/ubuntu byte drift dance
- marks saved as 8-bit palette PNGs (FASTOCTREE quantize keeps per-index
alpha tRNS -> 2.5KB, AA edges preserved); compose_svg renders in the
background's native space so resvg scales per output size
The icon pipeline now composes all 35 targets from two source axes instead
of a single hand-edited master:
- girl art: assets/nous-girl-black.svg / nous-girl-white.svg (brand kit)
- backgrounds: assets/backgrounds/ (squircle light/dark, logo frames,
BrandMark tiles)
assets/icon-master*.svg are generated artifacts (squircle background + girl
nested in the 824px HIG content safe zone). New dark-appearance artifacts
(icon-dark.* containers, appx *-dark logos, logo-dark wordmark,
nous-logo-dark) land everywhere a surface consumes them: docusaurus navbar
srcDark, BrandMark dark tile (keyed off renderedMode).
nous-girl.jpg jpgs are replaced by lossless 8-bit palette PNGs saved with
compress_level=0 (stored deflate blocks, byte-identical across hosts for
the CI freshness lane).
Includes the temp linux-truth harvest step in icons-freshness-check.yml so
the committed compressed bytes can be refreshed from the ubuntu regen.