Seven per-file tables moved: S.retouch, S.logo, S.social, S.tools, S.wiring, DIRECTOR_WARNINGS, and doctor.ts's table merged into the existing S.doctor (verified collision-free first). Each file keeps a local alias -- 'export const R = S.retouch' -- so not one reference site changed, which is what keeps a 250-line move reviewable. DIRECTOR_WARNINGS stays a named export rather than a nested key: it is a table of functions with its own Record<DirectorWarningCode, ...> type. The type is pulled in with 'import type', erased at compile time, so no runtime cycle even though director.ts imports S from here. tsc proves the keys exist but not that they resolve at run time, so tests/e2e/strings.mjs loads the modules through jiti and asserts real leaves are non-empty and the warning functions still render Italian. Also documented: pi needs Node >= 22.3 (pi-ai calls process.getBuiltinModule). 0 TODOs left in extensions/.
5.2 KiB
Platform requirements and unverified assumptions
Written down because these bite on someone else's machine, not on the one where the code was authored. Everything below was established by reading source or vendor docs; items marked UNVERIFIED need the actual Mac to settle and are flagged in the code too.
Node version — check this first
pi needs Node >= 22.3. @earendil-works/pi-ai calls process.getBuiltinModule, which
landed in Node 22.3; on Node 20 every module that reaches pi-ai fails to load with
process.getBuiltinModule is not a function. pi publishes a legacy-node20 dist-tag for
exactly this, pinned to 0.74.2 — far behind the current 0.84.x — so the right answer is a
current Node, not the legacy tag.
node -v # want v22.3 or newer
brew install node
tests/e2e/strings.mjs detects this and reports the affected module loads as skipped
rather than failed, so the suite stays honest on an older Node.
macOS version matrix
The stack does not have one minimum version — it has three, and the highest one only affects an optional feature.
| Component | Minimum | If unavailable |
|---|---|---|
draw-things-cli |
macOS 13 (Homebrew Core, arm64 bottles only) |
Nothing works — this is the floor |
typst |
macOS 11 | Nothing renders |
native/cutout.swift (Apple Vision) |
macOS 14 | Exits 3; cpu.ts reports it in Italian and falls back to rembg |
⚠️ The cutout helper needs a newer macOS than everything else. It uses
VNGenerateForegroundInstanceMaskRequest, which is macOS 14+, while the rest of the
stack runs on 13. So on macOS 13 everything works except one-step background removal,
which degrades rather than failing:
/logostill generates and vectorises; the cutout falls back torembgif installed.- ⚠️ If falling back, never accept rembg's default model:
bria-rmbgis CC BY-NC 4.0 and not usable commercially. Passisnetoru2netexplicitly. - rembg also never touches the Neural Engine unless given the undocumented
-x '{"providers":["CoreMLExecutionProvider"]}'— silently slow otherwise.
Apple Silicon is required outright: draw-things-cli ships arm64 bottles only, and
there is no Intel build. install.sh refuses non-arm64 with a clear message.
native/cutout.swift is not compile-tested — this repo was authored on Linux, with
no Swift toolchain and no Vision framework. It is written in the single-file swiftc
pattern, but its first real compile will happen on the Mac.
Unverified assumptions in the backend
| Assumption | Risk if wrong |
|---|---|
Units of decodingTileWidth/decodingTileHeight — pixels assumed, matching the 512/512/64 defaults |
If they are latent units, each tile covers 8× the area and the memory saving is smaller than intended. Flagged on tilingPayload(). |
No --strength CLI flag exists, so img2img strength travels inside --config-json as the strength key |
Strength silently ignored; edits come back too weak or too strong |
Which lever an upscale model expects — the configured model is driven as the main --model over --image at low strength; ESRGAN-family names also set upscaler/upscalerScaleFactor |
Upscale falls back to a Lanczos3 resample via sharp, announced in Italian, with model: "lanczos(sharp)" in the result so the caller can tell |
Daemon argv assumed <modelsDir> --no-tls --port N [--cpu-offload], models dir positional and first |
Daemon fails to start; doctor reports it |
| Readiness = a TCP accept on the port. Without FlatBuffers we cannot ask the daemon anything over gRPC | "Porta aperta" means reachable, not warm. The first generation may still pay a cold model load. |
A Node fetch download does not carry com.apple.quarantine |
xattr -d runs best-effort and its failure is non-fatal |
| Download integrity is structural, not cryptographic — size ≥ 1 MiB plus a Mach-O magic number; no published checksum exists to pin | A tampered-but-valid Mach-O would pass |
| Timeouts (60 s ready budget, 5 s SIGTERM grace) chosen without a machine to measure on | The ready budget is the one most likely to need raising on a cold first model load |
Also deliberate: -w/--weights-cache is never passed. Its 0 GiB default is correct on
16 GB — raising it steals exactly the RAM the VAE decode needs.
Draw Things works in 64 px units, so requested sizes are snapped up to that grid (never down: art slightly larger than the frame can be cropped, art slightly smaller would have to be upscaled). Real dimensions are read back from the produced file, so a caller is never told a size the image does not have.
Still open upstream
Draw Things issue #121 — img2img crashes with EXC_BREAKPOINT on 16 GB Macs on the
current build, via both the API and the UI, unresolved. txt2img is unaffected. Detection
is graded rather than binary: an abnormal signal or an explicit Mach-exception fingerprint
gives confident Italian wording, a bare dead-transport marker gives hedged wording
("con ogni probabilità"), and the diagnosis is only ever offered for runs that passed
--image. See docs/OPEN-DEFECTS.md for what still works when it bites.