Skip to content

v0.3.5 — beta

Pre-release
Pre-release

Choose a tag to compare

@OuincheWinch OuincheWinch released this 05 Oct 21:13
· 5 commits to main since this release

Tested on the owner's own machine, from the installed build, before publication.

Fast VAE decode was dead in every previous build

Turning on fast VAE appeared to do nothing, and the reason was that TAESD never ran. taesd_mlx.py resolved its weights from __file__/data, which inside the signed bundle is inside the signature, so the lookup missed and every SDXL render quietly fell back to the full VAE inside an except that printed one line to stderr nobody reads. It now resolves through the model store, so it follows store_path too.

Verified in the installed app: a 128×128×4 latent decodes to 1024×1024×3, and a real generation logs zero TAESD warnings. The owner reports the speed difference is immediately obvious.

The bundle named the build machine in 88 files

82 console-script shebangs, both pyvenv.cfg home lines, six venv activate scripts, a PEP 610 direct_url.json and three docstrings all carried the build machine's volume path. It worked on the machine that built it, which is exactly why it was never noticed — and it would have failed on every other Mac.

Fixed by construction rather than by computing a better absolute path, because there is no correct absolute path for something that can be dragged anywhere. pyvenv.cfg now carries a relative home matching the existing relative symlinks, and each console script becomes a #!/bin/sh trampoline that finds its own interpreter:

'''exec' "$(dirname "$0")/python" "$0" "$@" # '''

The build now refuses to ship on an absolute shebang, an absolute home, or a build path anywhere under bin/. It then rehearses the relocation: copies the finished bundle to another path and asserts both interpreters report a base_prefix under the new location, that mlx and torch import, and that a trampolined script executes. That rehearsal earned its keep immediately — it caught a /var vs /private/var comparison bug in the check itself, and a metadata scrub that had been left running after signing, which invalidated the seal.

The build installs itself now

Both hand-rolled copies failed silently. cp -R produced a bundle 1,973 files short and reported success; ditto on APFS-over-USB exits 0, copies nothing, and merges into an existing bundle, leaving a hybrid with a broken seal and a stale Info.plist. INSTALL_APP=1 ./build_app.sh removes the target, copies, then proves the result by comparing file counts and re-verifying the signature — a mismatch is a loud failure rather than a broken app found later.

Size selection

The size control multiplied a base resolution by a ratio, which produced sizes no model was trained on — 16:9 at base 1024 is 1024×576, and it looks it. It is now a model-aware picker: four orientation tabs over exact native resolutions, with Custom for anything else, and Custom reports the true ratio of what you typed. Sizes below 768 px are offered by default; a filter that hid them was added and then removed, because it cost a line of height to hide exactly one button, did nothing on two of the three tabs, and silently resized the image when ticked.

SDXL models are checked before you wait 2 minutes to find out

The old validation searched recursively for any .safetensors anywhere, so a single-file checkpoint — how almost every SDXL model is published on Hugging Face — passed and then failed at generation with a forty-line traceback ending in No .safetensors files in .../unet. An SDXL engine now requires the diffusers layout at adopt time and names the missing part.

SDXL: discovery and loading, fixed

Reported from a second machine where Juggernaut XL Lightning would not load.

Every SDXL model read "Not runnable here". The table that decides which
architectures this app can execute covered FLUX.2, Z-Image, Krea and Qwen — and
had no SDXL entry at all. So even RunDiffusion/Juggernaut-XL-Lightning, the
registry's own repository, was listed as unusable while showing an INSTALLED badge
on the same row. Four SDXL engines are now recognised.

A ControlNet is no longer offered as a checkpoint. Name matching alone is not
safe: juggernaut-xl-lightning-4step-controlnet-coreml-6b contains "juggernaut",
"xl" and "lightning". It would have cost a multi-gigabyte download to reach a
dead end, so ControlNets, LoRAs, VAEs and upscalers are rejected up front.

A failed load now says what to do. It previously produced a forty-line
traceback ending in No .safetensors files in .../unet. The four possible causes
get four different messages, because they need different fixes:

cause what you are told
interrupted download "unfinished download (N MB still partial) — re-download"
single-file checkpoint "needs the diffusers folder layout — convert it"
empty unet/ "the download is incomplete"
not installed "does not exist, so the model was never installed"

A download is no longer marked installed unless it worked. The task was
declared successful on the strength of the transfer loop finishing, without ever
looking at what had been written. That is how an interrupted 9.5 GB shard left an
empty unet/ behind an INSTALLED badge. The layout is verified before the task is
reported done.

The app moves port instead of refusing to start

If something else already owns 127.0.0.1:8001, the app used to show an alert and
give up. 8001 is a popular port — a dev server, an older build, an unrelated app —
and none of those should stop this one from launching.

It now tries each rung of 8001, 9025, 10049, 11073 … 17217 (ten rungs, +1024)
and takes the first that is free. If a rung is already serving a DiffusionBear
backend
, that one is reused instead, so opening the app twice does not leave two
engines resident with two copies of the model in memory.

Telling our backend apart from someone else's matters here, and it is not as easy
as "something answered": a 404 from an unrelated server looks identical to a healthy
answer under that test. So the probe asks for /api/version and requires JSON that
names DiffusionBear and carries a version. A timeout, an empty body, an HTML error
page, or JSON for another service all mean "not ours, try the next rung" — and a
listener that accepts the connection but never replies is abandoned on a bounded
wait rather than hanging the launch.

No proprietary fonts

The app embeds no fonts — no @font-face, no web font in the bundle — so there
is nothing to redistribute and nothing to license.

The interface names fonts, as a native Mac app does: CSS keywords the browser
resolves (system-ui, ui-monospace), macOS system fonts macOS provides
(-apple-system, Menlo, SFMono-Regular), and open fallbacks (DejaVu Sans Mono, Liberation Mono — Bitstream Vera and SIL OFL 1.1).

An audit of 0.3.5 found Consolas in two stylesheets: a Microsoft font,
proprietary, and not present on a Mac without Office, so it could only ever
resolve on a machine that already had it. Removed. backend/test_font_licensing.py
now enforces this and is default-deny — any font name that is not a CSS
keyword, a macOS system font, or a known open font fails, because a denylist only
catches what someone already thought of.

The 38 TTF files inside venv/ are matplotlib's, arriving with a dependency no
backend module imports. All three families are free, verified against the licence
text that ships beside them: DejaVu (Bitstream Vera + public-domain changes), STIX
(SIL OFL 1.1) and Computer Modern (Knuth, public-domain lineage).

Also

The i18n catalogue parser demanded exactly two leading spaces while two files indent some entries four and one zero, so 23 translations were never actually checked. The parser now accepts two or more and the bound is exact zero instead of a documented 23.


Requirements unchanged: Apple Silicon, macOS 15 or later, right-click → Open on first launch because the app is ad-hoc signed and not notarised.

Verify a download with shasum -a 256 against ALL_SHA256SUMS. Model weights are not included; they download on first use from Hugging Face or Civitai into ~/Library/Application Support/DiffusionBear.