The DMG opened as a plain Finder window. It now shows the app icon's figure on
the LightCraft colour, with LightCraft.app and the Applications link side by side.
Same approach as VectorCraft's (storytold/vectorcraft#493).
- packaging/macos/dmg/: background.svg (source; text outlined, the figure an
<image> of assets/app-icon), background.tiff (1x + 2x HiDPI, Deflate, sRGB)
and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
reproducible, with nothing from the machine that ran it (no paths, users,
disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
an alias that includes the volume name. The DMG file name keeps the version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Release: FreeBSD x86_64, Flatpak x86_64/aarch64, AppImage zsync updates; README Downloads
- linux: appimagetool -u embeds gh-releases-zsync update information; the .AppImage.zsync is
published beside each AppImage (zsync installed; checked in the job).
- flatpak: new job per arch repackages the Linux tarball into a single-file .flatpak
(packaging/linux/flatpak-bundle.sh + ai.storyteller.lightcraft.bundle.yml); the Flathub
manifest gets the CLI and the same commented finish-args; packaging-lint checks they agree.
- freebsd: new job builds lightcraft-<ver>-freebsd-x86_64.tar.gz in a FreeBSD 14.3 VM with the
freebsd.yml packages (packaging/freebsd/package.sh; layout dry run in packaging-lint).
- version, linux and web no longer use the release environment (they sign nothing), so a
branch dispatch dry-runs every unsigned job; the release job still needs macOS and Windows.
- README: Downloads section listing every build; docs/releasing.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Linux packages: describe LightCraft, not PhotoCraft, in the deb/rpm description
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The standard macOS runners are shared with all CI and queue for an hour or
more. The macos-release runner (GitHub-hosted M2 Pro) is in a runner group
usable only by release.yml on the release branch, with a monthly spend cap
on the actions_macos_xl SKU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* About: contributors compiled in from contributors.json (Contributors + Models tabs, grab bag + table, sortable, name toggle)
Adds the craftrules standard (standards/contributors.md) to LightCraft: contributors/contributors.json
is generated by craftrules/scripts/contributors.py and baked into the binary by
crates/ui-egui/build.rs (merged next to the tr_format! generation) as static tables. The About
dialog gets tabs About · Contributors · Models. Each contributor line: @username, opt-in
display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit. Sortable by
every column; alphabetical ignores case and the leading @. Tabs and the list view are
control-channel widgets (button:aboutTab-*, button:creditsTable / button:creditsGrabBag).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors docs: craftrules is two levels up (../../craftrules)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section
build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate (verified people.toml entries)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate after rebase onto main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate (verified people.toml entries)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
load_bytes_now timed the binned demosaic with std::time::Instant, which
panics on wasm32-unknown-unknown ("time not implemented on this
platform"). Opening a raw in the web build aborted the decode. Use
web_time::Instant, as library.rs already does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Installed RAM overcommits a busy machine: on a 32 GB desktop with browsers,
a VM and editors open, about 7 GB was free and the installed-RAM rule chose
16 jobs. A fresh build then pushed free RAM below 1 GB.
`cargo xtask ci` now uses one build job per 1.5 GB of RAM available when it
starts (Linux MemAvailable, Windows AvailableMBytes, macOS free + inactive +
speculative pages), capped at the CPU count. With line-table debuginfo,
4 jobs measured about 3 GB in use, so this leaves about half the available
RAM spare. If available RAM can't be read, half the installed RAM is
assumed; if neither can, 4 jobs. Test threads follow, capped at 4.
Explicit CARGO_BUILD_JOBS / RUST_TEST_THREADS still win.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#207 let up to two edge pixels differ because the GPU's f32 framing could
fall on the other side of the lens-warp boundary. 'GPU: decide warped-geometry
edges with a CPU coverage mask' (this PR) makes the GPU use the CPU's f64
decision, so every case is back within the plain MAX_LSB check (embedded
lens + perspective: max 1 LSB, was 91 before the mask).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
settle() returns after a quiet spell, which on a loaded machine can come before the first grid
thumbnails, the profile browser's variant thumbnails or the async import review (and its
thumbnails) land. demo_grid_snapshot_has_ui_pixels, profile_browser_previews_on_hover_and_applies_on_click
and import_review_dialog failed that way when two test runs shared the machine; they (and the
two other import-review tests) now step until what they assert is there. 2 concurrent runs: 138/138 each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained Headless::step_until for the same job as this branch's
test-only wait_for_widget (run frames until a condition holds). Use the one
helper and drop the duplicate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reopening a library (or opening another one, or set_disk_cache_bytes
falling back to attaching) replaced MediaCache::rendered with a new
PreviewCache, but render jobs still in flight kept the old object.
Each PreviewCache checked only its own generation, so after a clear on
the new cache the old one's generation was still current: a finishing
thumbnail job wrote its pixels through it into the directory the clear
had just emptied. The UI rejected the result, but the JPEG stayed.
attach_disk_cache now retires the cache it replaces before swapping it:
PreviewCache::retire takes the generation write lock (so it waits for a
write in progress, like clear) and marks the object retired; every
get/put/put_deferred through it is then refused and its clear() is a
no-op, as its directory may now belong to the replacement. Files on
disk are left alone, so a plain reopen still finds its thumbnails, and
resizing the budget in place keeps the same cache object as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The main thread's Workers tracked no rendered-thumbnail cache until the
first render request carried one. If Clear Preview Cache ran before that
(e.g. starting on an empty filtered view), the per-frame observation in
finished() had nothing to compare with, and the first request only
recorded the already-cleared generation as its baseline. The persisted
ThumbIndex was never invalidated, so once the filter was removed the
request touched the old entry and the worker read the pre-clear JPEG
back from browser storage. Replacing the cache (another library) before
the first request had the same gap.
Workers::start now takes the session's cache and records it, with its
current generation, as the baseline the persisted index belongs to. A
normal start with no clear observes the same cache and generation and
keeps the index. The comparison moves into wire::CacheWatch so it is
tested natively (cargo test --workspace) next to ThumbIndex.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#243 dropped every result whose request id wasn't the newest for its slot. The loupe requests a new
draft every frame while a slider moves, so when a draft took longer than a frame nothing landed
until the drag paused. Main, Before and Hover now take a superseded draft that is newer than what
they show (never one from before a library or preview-cache reset, never an error or its source).
Also saturate the xtask RAM rounding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>