42 Commits
Author SHA1 Message Date
Fabio Lissi 70e182b08f panic_hunt: don't fuzz the print commands into a real printer (#4467)
* panic_hunt: don't fuzz the print commands into a real printer

file.print and file.printOneCopy spool to CUPS, so every run of the adversarial-params test
printed a blank page on the default printer. The hunt now skips those two ids (their params
are validated like everyone else's; spooling is not what the test is for).

* Guard the print spooler itself and the CI environment

- print_cmds: disable_printing() (a process-wide flag the panic hunt sets) and the
  PHOTOCRAFT_NO_PRINT environment variable make spool_pdf refuse before running lp; dryRun
  still renders and reports the command. Test.
- CI: PHOTOCRAFT_NO_PRINT=1 and LPDEST=photocraft-no-such-printer, so a regression fails
  loudly instead of printing. Documented in docs/development.md › Environment variables.
2026-10-12 23:13:42 -04:00
Brandon ThomasandClaude Opus 5.5 3d6050b931 Release workflow: pass the Azure signing secrets so Windows builds are signed (#3794)
Since the release workflow moved to storytold/craft-common-actions, it has
passed only the Apple and WINDOWS_CERTIFICATE secrets. The signing job reads
values from the release environment, but only for names the caller lists, so
the AZURE_* names that Trusted Signing uses came through empty and Windows
builds would ship unsigned. Same fix as storytold/craft-launcher#174.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 11:08:34 -04:00
agent-346022andphotocraft-agent UNCONFIGURED 649b1d5d92 psd: harden the standalone binary parsers (fuzz + property tests, gradient/swatch correctness) (#3542)
* psd: harden the standalone binary parsers (gradients, swatches, patterns, filter effects, metadata, resources, tagged blocks)

Correctness and robustness work on the psd crate's auxiliary parsers, with
fuzz targets and property tests so the invariants are enforced, not just asserted.

grd (gradients):
- Split num()/finite() into a single guard with a saturating f64->f32 cast: only
  NaN has no value, everything else (including +/-inf and 1e300) saturates to the
  representable range so the mapping stays monotonic instead of snapping to a field
  default and inverting a hostile value.
- Hue now wraps with rem_euclid(360) instead of clamping (370 -> 10, not 370 -> 360,
  which would be a different colour at the boundary).
- Every colour model is bounded: RGB/CMYK/Gray to 0..=1, L* to 0..=100 and a*/b* to
  the CIELAB gamut (-128..=127), so a downstream converter never sees out-of-gamut input.

aco/ase (swatches):
- Share one io::utf16_units() helper that caps a name by UTF-16 code units without
  ever splitting a surrogate pair; keeping it in one place stops the fix from drifting
  between the two formats.

Coverage:
- New fuzz targets for grd, aco, ase, patterns, filter_effects, metadata, tagged and
  resources (wired into the fuzz CI matrix).
- Property tests mirroring each fuzz invariant (round-trip stability, no panic on
  arbitrary bytes, documented colour/number ranges) so normal CI covers them; the
  fuzz jobs run on demand.

369 psd tests pass; clippy -D warnings and rustfmt clean; layering and wasm check pass;
all psd fuzz targets build and run clean.

* psd: address Gemini/Claude review of the parser hardening

Follow-up to the previous commit, applying the substantive points from two
independent code reviews (Gemini 3.8: ship; Claude: ship after small fixes).

Production:
- grd hue: an in-range value (0..=360, including exactly 360) is now kept verbatim;
  only an out-of-range hue wraps with rem_euclid. Wrapping unconditionally altered a
  legal 360 to 0 (and f32 round-off on a tiny negative could itself land on 360),
  changing the colour at the boundary for anything that interpolates in HSB.
- ase: compile-time assert that MAX_NAME_UNITS < u16::MAX, since put_name writes the
  length as `units.len() as u16 + 1`; guards the invariant if the cap is ever raised.

Stronger test/fuzz invariants (byte stability alone would pass even if the writer
silently dropped a field; these assert semantic equality):
- metadata fuzz: assert the parsed-then-written block re-parses to the same items
  (the weakened byte-only check was the fix the first fuzz run surfaced).
- patterns fuzz: assert parse(write(parse(x))) == parse(x); channel offsets/padding
  are wire artifacts, not fields, so the round trip is lossless (confirmed, 2M execs).
- filter_effects fuzz: assert back == fx as well as byte stability; also decode every
  cached slot, not just the mask.
- resources fuzz: round-trip ResolutionInfo through to_bytes/from_bytes.

Coverage:
- tagged fuzz: select the block key from a table of every typed/strict key via the
  first byte, so the fuzzer reaches each branch instead of guessing a 4-byte key.
- new property tests: damage-a-valid-block for metadata/patterns/filter_effects (pure
  random bytes rarely pass a header), and check_structure over every structural key.
- grd_every_colour_model_is_clamped_and_finite now pins exact saturation values
  (a*/b* to +/-128, L* to 0/100, units to 0/1), so a weaker clamp is caught, not just
  an out-of-range value; grd_hue_wraps now pins the exact-360 and 0 boundaries.

Verified: 371 psd tests pass; clippy -D warnings, rustfmt, layers and wasm clean; all
13 fuzz targets build and run clean (metadata 11.5M, patterns 2M, filter_effects 5.9M,
resources 3.5M, tagged 5.4M execs); the a*/b* clamp-weakening and hue-guard-removal
mutants are both killed by the strengthened tests.

---------

Co-authored-by: photocraft-agent UNCONFIGURED <[email protected]>
2026-10-12 02:49:08 -04:00
skylar taylor-barrick 2bf49f10a7 Security: verify AppImage build inputs (#2270)
* security: pin and verify release build inputs

* docs: explain verified offline AppImage inputs

* security: validate executable AppImage tooling offline

* security: preserve read-only verified AppImage caches

* test: cover verified runtime downloads and cache diagnostics

* test: assert verified runtime mode and cache diagnostics

* Fix shellcheck ordering in verified download test

* Use external chmod for cache test setup
2026-10-11 22:33:26 -06:00
Dylan M. Taylor 19f427aa00 ci: refactor workflows to use storytold/craft-common-actions (#3432)
* ci: refactor workflows to use reusable artcraft-common-actions

* ci: point reusable workflows to storytold/craft-common-actions

* ci: pin storytold/craft-common-actions to commit 27a978f and pass secrets explicitly

* ci: use @main (unpinned) for craft-common-actions with explanation comment
2026-10-11 11:50:48 -04:00
Brandon ThomasandClaude Opus 5.5 7926161595 Release prep 0.7.0: Linux riscv64 tarball + version bump (#2965)
* Release prep 0.7.0: add Linux riscv64 tar.gz to the release; bump to 0.7.0

- release.yml: new linux-riscv64 job, ported from GridCraft: cross-compiled on ubuntu-24.04
  against ports.ubuntu.com riscv64 libs, tar.gz only, photocraft-cli --version smoke-tested
  under QEMU. The draft-release job now needs it, so SHA256SUMS.txt covers it.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support.
- linux_libs: search /usr/lib/riscv64-linux-gnu on riscv64 (was the x86_64 triplet).
- README and docs/releasing.md list the riscv64 tarball.
- Version 0.6.0 -> 0.7.0 (cargo xtask version set), Cargo.lock refreshed; contributors refreshed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* canvas: crisp elliptical marquee ants again in an unrotated view

#2732 (a27d7175) built the elliptical marquee's live ants in document space and snapped every
vertex to the half-pixel grid on its own. Near the edge midpoints the nearly flat arcs became
1 px stairs, whose antialiased steps are grey, so the ants no longer contrast on white or black:
drag_preview_canvas's marquee test fails on main (ubuntu and windows). In an axis-aligned view
(rotation a multiple of 90 degrees) draw the ellipse inscribed in the snapped screen box again,
as before #2732; rotated views keep the mapped ellipse.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 21:36:30 -04:00
d502c5de27 ci(web): validate browser builds and wasm size on pull requests (#1689)
* ci(web): build browser release and enforce wasm gate on PRs

* docs(web): describe PR browser build and size checks

* CI web gate: run only when web-relevant files change (main always)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 20:05:15 -04:00
BFlatandClaude e39cacd895 Packaging: Windows MSI setup wizard (folder choice, progress, version, launch on finish) (#1864)
Double-clicking the MSI now opens WixUI_InstallDir without the licence page:
Welcome, install folder (remembered for upgrades in
HKLM\Software\PhotoCraft\InstallDir), Ready, progress, and a Finish page with
a ticked "Launch PhotoCraft" box (WixShellExec, unelevated). The pages name
the full version, pre-release tag included (photocraft.en-us.wxl).
package.ps1 draws the banner and side bitmaps from the app icon, so no WiX
stock art ships. msiexec /qn still installs silently. The release and ARM64
workflows install the WiX UI and Util extensions; packaging-lint checks the
.wxl. Ported from VectorCraft (storytold/vectorcraft@aa548d2).


Claude-Session: https://claude.ai/code/session_01AP7ThRN3kpNmjsuJUerJui

Co-authored-by: Claude <[email protected]>
2026-10-09 21:33:38 +07:00
bb3108cfd2 CI: no macOS on PRs or main; macOS is built and tested by releases (#1820)
The org has 5 macOS slots in total (GitHub Team), shared by every repo's CI and
release builds, and PR CI kept them full, so release builds queued behind it.
macOS now runs only in release.yml; a release that fails on macOS is fixed and
rebuilt.

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 08:51:18 -04:00
callmeBallochandClaude Opus 5.5 a8d7c6ef3f AF.3: put the Affinity fuzz targets and corpus back in CI
The maintainers approved the new crate, its fuzz targets and the corpus
upstreams in principle (storytold/photocraft#1607), so this undoes the
interim opt-in: ci.yml fuzzes preview and import_preview again, corpus
--all fetches the Affinity set, and its test runs under the normal
corpus feature. The provenance section stays.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 06:38:40 +00:00
callmeBallochandClaude Opus 5.5 edb42922c1 AF.3: keep Affinity out of CI's corpus and fuzz jobs; state the clean-room provenance
Review on storytold/photocraft#1607 asked maintainers to decide on new
fuzz targets in ci.yml and four third-party corpus upstreams in
corpus --all. Until they do, neither touches CI: ci.yml is main's again
(the targets stay in crates/affinity/fuzz, run locally), and the
Affinity corpus is fetched only by corpus --affinity and tested with
--features corpus-affinity, so --all and test-corpus are unchanged.
The plan is to move those files into photocraft-corpus.

The crate README now spells out the provenance: no Affinity software,
afread's MIT description re-described without translating code, every
structure confirmed on public files (CRC-32 per entry, self-typed
fields), semantics fitted to Affinity's own thumbnails, and how the
sample documents were licensed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 05:19:56 +00:00
callmeBallochandClaude Opus 5.5 57ae2fb1a3 Merge storytold/main into AF.3 native Affinity import
Conflicts: File › Open and batch extension lists (SVG, swatches and
Affinity together); the import dispatcher (upstream's type-layer
preparation with the new result fields); the pinned EXR and Affinity
corpora side by side; README, book and development docs.

Affinity type layers now draw through text_import::prepare, so they get
the same raster bound as other imported type, and SVG import fills the
two new ImportResult fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 04:26:18 +00:00
BFlatandClaude Opus 5.5 5a2f42c476 CI: build faster and stop running out of disk and cache (#1075)
- Debug/test builds use line-tables-only debug info: the workspace test
  build is 1.9x faster (509 s -> 271 s, measured on Windows) and its
  target dir 31 GB -> 13 GB, which also fixes the Linux job's craft-fonts
  build running out of disk.
- The corpus job's --release build drops thin LTO and the single codegen
  unit: 2.7x faster to build (744 s -> 273 s); it was 23.5 of 37 minutes.
- Only main saves rust-cache entries (PRs restore main's), with
  cache-on-failure, so per-PR copies no longer evict main's caches from
  the 10 GB budget.
- The Linux job deletes unused image toolchains in the background.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 02:46:00 +07:00
callmeBalloch e3932fa7a3 Merge remote-tracking branch 'upstream/main' into claude/kind-darwin-btjse2 2026-10-08 16:08:36 +00:00
callmeBalloch 1bb37eda05 AF.1: import bounded Affinity previews with fidelity warnings
Read the indexed version-12 PNG preview with bounds, chunk ordering and CRC checks. Show native-content and resolution warnings, preserve the source through normal and automation opening, and reject auxiliary routes that cannot display the warning. Add format documentation, malformed-input and save-lifecycle tests, native-oracle hooks and parser/import fuzz targets. Editable native layers and Affinity export remain unsupported.
2026-10-08 17:42:23 +02:00
bce7e54a63 Release workflow: only signing and draft-release jobs use the release environment (#1220)
version, linux and web read no secrets. With environment: release they were refused on a branch,
so a branch dispatch of release.yml built nothing. Now a dry run builds every unsigned platform;
the signed jobs are still refused and the draft-release job needs them, so a branch can't create
a release. Matches craftrules release/playbook.md §2.

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-08 09:52:09 -04:00
Tanish MankuandClaude Opus 5.5 fee08b07b6 CI: craft-fonts run uses the Test step's exact build again (Linux runner out of disk since #800) (#955)
#800 narrowed the craft-fonts test run to -p photocraft-text -p photocraft-ui-egui. A
different package and feature selection resolves a different feature set (no
--features heif), so cargo recompiled the whole dependency graph into the same
target dir on top of the Test step's build, and test (ubuntu-latest) ran out of
disk (No space left on device, linker bus error) on main and every PR.

Back to cargo test --workspace --locked --features heif, which reuses the Test
step's artifacts and rebuilds only the crates that read CRAFT_FONTS_DIR. The comment
says why the selection must match.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 19:31:43 -07:00
Brandon ThomasandClaude Opus 5.5 e040376e15 Release CI: macOS job on macos-15-xlarge
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 <[email protected]>
2026-10-07 22:07:53 -04:00
Brandon ThomasandClaude Opus 5.5 1d8a32ed83 Release CI: macOS job on the org macos-release runner
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 <[email protected]>
2026-10-07 21:54:51 -04:00
Tanish MankuandClaude Opus 5.5 07174271d5 CI: never cancel main's runs; trim per-PR runner time (#800)
Main's CI was effectively unverified: with cancel-in-progress on every ref, each merge
cancelled the run of the one before. Between 09:56 and 20:34 UTC on 2026-10-07, 80 of 83
push runs on main were cancelled, and a formatting break from two PRs that were each green
alone (#663 + #700) went unnoticed until PRs updated to main started failing.

- ci.yml: cancel-in-progress only for pull_request. Main runs finish; a merge that lands
  meanwhile waits and runs next.
- ci.yml: a `platforms` job picks the test matrix. main, manual runs and PRs labelled
  `full-ci` get Linux, macOS and Windows. Other PRs get Linux only, unless they touch
  code whose behaviour differs by platform (apps/, ui-egui, text, gpu, tablet, heif,
  build.rs, Cargo.lock, ci.yml).
- ci.yml: the second Linux test run with craft-fonts covers only the crates that use the
  fonts (photocraft-text, photocraft-ui-egui), not the whole workspace again.
- freebsd.yml, windows-arm64.yml: nightly on main instead of on every push and every
  Cargo.toml change; PRs touching their own packaging still run them.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 19:46:18 -04:00
bd632e7eb3 Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) (#672)
* Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215)

Photoshop keeps a layered TIFF's layers in two private tags: 37724 (ImageSourceData, the same
layer section a PSD has) and 34377 (the image resources). PhotoCraft now reads and writes both,
so a TIFF opens with its groups, masks, layer styles, type, shapes, smart objects and adjustment
layers exactly as the PSD path would, and saves them back.

* photocraft-psd: a `tiff` module with `ImageSourceData` (parse/write, PSD- and PSB-sized),
  the 34377 resource helpers, and a field-by-field byte-order transcoder. In Intel-order TIFFs
  Photoshop byte-swaps the whole block structure (reversed keys, little-endian integers and
  doubles) while pixel samples stay big-endian; the transcoder knows the layer-info structure,
  masks, blending ranges and every block photocraft-io maps (descriptors, TySh, lrFX, the
  binary adjustments, paths, patterns, filter effects, metadata settings, linked layers,
  annotations...), recognizes descriptor-shaped unknown blocks, and drops the rest with a
  warning instead of passing byte-swapped data to other readers. The reader gained an optional
  little-endian mode. 23 unit tests; a fuzz target in the CI matrix.
* photocraft-codecs: the two tags travel as opaque bytes on `Metadata`, read by a
  bounds-checked walk of IFD0 (classic and BigTIFF, both byte orders) and written back as
  BYTE / UNDEFINED tags. `tiff_writes_little_endian()` tells which order the encoder produces.
* photocraft-io: `tiff_layers` rebuilds a PSD model from the TIFF's pixels plus the tags on
  import, and on export runs the PSD exporter (new `merged_matte` / `merged_compression`
  options), interleaves its merged planes for the TIFF image and stores the layer section in
  the encoder's byte order. `ExportOptions.tiff_layers` (default on). Lab and Multichannel
  documents save flat; extra alpha channels are not written (one transparency sample).
* UI: the TIFF Options prompt ("Including layers will increase file size." / "Discard Layers
  and Save a Copy" / "Don't show again") behind Preferences > File Handling > Ask Before
  Saving Layered TIFF Files; `ExportSettings.tiff_layers`; catalogs updated.
* Tests: io round trips at 8/16/32 bits in Gray/RGB/CMYK, equivalence with the PSD path, every
  TIFF compression, the flat fall-backs, malformed input, and files written by psdtags in both
  byte orders (fixtures from scripts/layered_tiff_fixtures.py); a corpus test (feature
  `corpus`) writes every corpus PSD as a layered TIFF and compares the result with the PSD
  round trip (700 files, 0 differences, 0 crashes). Codecs tests cover the tag walk on
  hand-made files.
* Scorecard FILE-215-7 done, BUG-204-3 partial; README and book updated.

Co-Authored-By: Claude Mythos 5.1 <[email protected]>

* Layered TIFF: de and pt-br strings; neutral wording in the psd tiff docs

The four psdtags fixtures stay out of the repository (no binary test data here): the
generator now writes them to the gitignored corpus/layered-tiff/, and the tests that read
them skip with a message when absent.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Regenerate docs/scorecard.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: leon <[email protected]>
Co-authored-by: Claude Mythos 5.1 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-07 17:10:35 -04:00
782ea69f35 fix(linux): AppImage registers its desktop entry and icon on launch (#617)
* fix(linux): AppImage registers its desktop entry and icon on launch

On Wayland the compositor resolves the dock icon from the window's app_id
(ai.storyteller.photocraft, already set) against a host-installed .desktop
file. An AppImage installs nothing, so every run showed the generic Wayland
logo (#593). The app cannot set the icon itself: winit 0.30.13 (egui 0.36.2's
pin) has an empty set_window_icon on Wayland; xdg-toplevel-icon-v1 landed on
winit's 0.31 line only.

- packaging/linux/AppRun is a script instead of a symlink: best-effort
  integration into $XDG_DATA_HOME before launch (desktop entry with Exec
  rewritten to the AppImage's path and TryExec dropped, hicolor icons
  copied), content-compared so an unchanged install costs a few cmp calls,
  refreshed when the AppImage moves, PHOTOCRAFT_NO_DESKTOP_INTEGRATION=1
  opts out, and no failure can block the launch (best-effort steps silent).
- packaging/linux/apprun-test.sh: 8-case smoke (passthrough, Exec rewrite
  with spaces, metadata preserved, idempotence, move refresh, opt-out,
  unwritable data dir, extracted AppDir). packaging-lint runs it and now
  shellchecks AppRun.
- Docs: releasing.md Linux section; README install paragraph.

Fixes #593

* AppRun: skip desktop integration when the AppImage path needs Exec escaping

A quote, backquote, dollar, backslash, percent or line break in the path would need
desktop-entry escaping (a line break could even add keys), so integration is skipped
and the app still launches.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 16:12:00 -04:00
Brandon ThomasandClaude Opus 5.5 d7df9d8aa6 Rename PrintCraft to PdfCraft (storytold/pdfcraft) (#766)
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 14:40:36 -04:00
47f9306fd0 Open HEIC/HEIF photos (#355) (#372)
* Open HEIC/HEIF photos (#355)

iPhones save HEIC by default and Photoshop opens it; PhotoCraft had no
HEIF code, so .heic was missing from File > Open and failed to decode.

Add Format::Heif to photocraft-codecs, decoded by heic-rs (pure Rust,
forbid(unsafe), MIT OR Apache-2.0, wasm-ready): single pictures and
grid-tiled photos, 8-bit to U8 and 10/12-bit to U16, auxiliary alpha,
ICC, EXIF and XMP. The container's irot/imir/clap are applied and EXIF
Orientation is rewritten to 1, so photos are never turned twice. Limits
are checked against the declared size before decoding. Read-only (no
pure-Rust HEVC encoder), listed in ASYMMETRIC_EXCEPTIONS.

File > Open (desktop, web) and the batch list accept heic/heif/hif.
Tests compare against Apple's own decode and a 16-bit source, cover
orientation, metadata, limits and every truncation; fixtures from
heic-rs and pillow-heif are attributed in ATTRIBUTION.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HEIF: pin heic-rs exactly and add a decode_heif fuzz target

Review follow-up: heic-rs is young and parses untrusted files, so pin it
(=0.1.1) and fuzz the HEIF path. The target covers both orientation
paths and is meant to run with -timeout so a hang is reported, not
only a panic. It is added to the CI fuzz matrix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HEIF: a heic-rs panic is a malformed-file error, not a crash

Fuzzing decode_heif found two out-of-range slices in heic-rs 0.1.1 on
malformed files:
- in its box parser (boxes.rs:130), reachable from a 72-byte file, even
  through the metadata-only probe;
- during HEVC reconstruction (hevc/decode/recon.rs:70).

The background job path catches panics, but the synchronous ones (jobs
off, the CLI) did not. The HEIF codec now runs the decoder under
catch_unwind and reports a panic as CodecError::Malformed, as
gpu/health.rs already does around wgpu.

The fuzz target also calls heic-rs directly, so its panics still reach
the fuzzer. The 72-byte input is a regression test, which fails
without the guard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* HEIF: move the decoder into an optional photocraft-heif crate

The heic-rs decoding from #372 now lives in `crates/heif` (photocraft-heif, standalone, depends
only on heic-rs =0.1.1): probe, decode with a pixel limit, container transforms, alpha, 8/10-bit,
ICC/EXIF/XMP and the catch_unwind guard, behind a small API of its own (Info, Options, Decoded,
Error). photocraft-codecs uses it behind a new non-default `heif` feature; the apps and
photocraft-io forward it. HEIF detection, extensions and mime stay compiled in, so without the
feature a HEIC file is still recognised and opening it is a clear "HEIC/HEIF support isn't
included in this build of PhotoCraft" error. `cargo xtask layers` allows exactly codecs -> heif
between standalone crates.

The fixtures are no longer committed: `cargo xtask corpus --heif` fetches them from heic-rs
(tests/fixtures, v0.1.1) and pillow-heif (tests/images) at pinned commits, verified against
xtask/heif-corpus.sha256 (same bytes as the PR's copies). The fixture tests run with
`--features corpus,heif` (`cargo xtask test-corpus` enables both).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Build CI and official binaries with HEIC support (--features heif)

CI test and clippy, the FreeBSD job, and the macOS, Linux (and Flatpak), FreeBSD, Windows and
web packaging all build with the `heif` feature; CI also tests codecs/io without it on Linux so
the "not in this build" path stays covered. `cargo xtask wasm` also checks photocraft-codecs with
`--features heif`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Docs: HEIC open is an optional build feature, on in official builds

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-07 10:20:40 -04:00
51d85c1170 Linux: AppImage update information and a .zsync beside it (#349) (#399)
* Linux: AppImage update information and a .zsync beside it (#349)

appimagetool now embeds
gh-releases-zsync|<owner>|<repo>|latest|photocraft-*-linux-<arch>.AppImage.zsync
(owner/repo from GITHUB_REPOSITORY, default storytold/photocraft), so
AppImageUpdate, AppImageLauncher and the like can find the next release and
fetch only the blocks that changed. With zsyncmake on the host it also writes
the .zsync, into its working directory, so the script runs it in $DIST and
warns when the file is missing.

The release job installs zsync and checks the embedded string and the .zsync
before uploading; docs/releasing.md lists the new asset.

Verified locally with packaging/linux/package.sh --skip-build --formats
appimage and zsyncmake from Ubuntu 22.04: --appimage-updateinformation prints
the string, and the .zsync's URL is the AppImage's basename.

* package.sh: absolute AppDir and appimagetool paths before running in $DIST

appimagetool now runs inside $DIST so zsyncmake writes the .zsync there,
which broke relative CARGO_TARGET_DIR / APPIMAGETOOL paths (AGENTS.md
suggests target/agent-<name>). Resolve both first, remove a stale .zsync
so a missing zsyncmake is reported, and list the .zsync and zsyncmake in
the header.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: bflatastic <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 05:26:01 -04:00
BFlatandClaude Opus 5.5 d9413cec0f FreeBSD CI: run the GPU integration tests one at a time (#576)
The VM's software Vulkan driver crashes (SIGSEGV) when several tests set up a GPU
device at once: about half of recent FreeBSD runs died within 0.05 s of a GPU test
binary starting 2-3 tests in parallel (drag_preview_canvas, color_managed_canvas,
adjust_preview_gpu), before any of them finished. Run photocraft-ui-egui's
integration tests with --test-threads=1 there; everything else is unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 16:19:48 +07:00
Brandon Thomas c161eaec73 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out (#353) (#353) 2026-10-06 18:21:46 -04:00
Brandon Thomas dd2f566daa macOS CLI: verify the shipped CLI is Developer ID signed and notarized in the release workflow (#308) (#320) (#320) 2026-10-06 10:48:05 -04:00
Brandon Thomas e8677ff851 Windows on ARM64: release builds, PE header check, install test on ARM64 hardware (#333) (#333) 2026-10-06 10:09:55 -04:00
Brandon Thomas a96a621dee FreeBSD release tarball and README platform lines (#284) (#284) 2026-10-05 20:54:00 -04:00
才倾梦华V 2172d085c6 Fix Windows MSI advertised shortcut icons (#238) (#238) 2026-10-05 18:13:51 -04:00
Brandon Thomas 968baf639e Photoshop oracles via storytold/photocraft-corpus: pinned corpora, corpus feature, test-corpus, CI job (#190) (#190) 2026-10-05 17:47:33 -04:00
Brandon Thomas 0f955e6c82 Scorecard: cargo xtask perf + scorecard, budgets, checklists, nightly perf (#221) (#239) (#239) 2026-10-05 10:53:02 -04:00
Brandon ThomasandClaude Opus 5.5 5e6db41fc2 FreeBSD: serialise canvas GPU tests, CI template, RAM detection, CJK font paths (#227) (#227)
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-05 09:58:58 -04:00
Brandon Thomas a0823c5e41 FreeBSD: build and test the workspace in a FreeBSD VM (#223) (#223) 2026-10-05 09:02:29 -04:00
Brandon ThomasandClaude Opus 5.5 231bf5fe81 Flatpak single-file bundles as release artifacts (#163)
* Flatpak single-file bundle as a release artifact (#140)

packaging/linux/flatpak/ai.storyteller.photocraft.bundle.yml repackages the
release's Linux tarball (no Rust build, no network) on the freedesktop 26.08
runtime; packaging/linux/flatpak-bundle.sh builds it with flatpak-builder and
flatpak build-bundle and smoke-tests it. release.yml gains a Flatpak job per
arch that feeds the draft release and SHA256SUMS. The Flathub manifest moves to
26.08 with the same documented finish-args (portals + Pictures/Documents), and
packaging-lint checks the two manifests agree. Font scanning also looks in
/run/host/fonts and /run/host/user-fonts so host fonts show up in the sandbox.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Docs: installing the Flatpak bundle (#140)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Flatpak job: install the SVG pixbuf loader for appstream compose

Without librsvg2-common (absent on the x86_64 runner image) the host's
appstreamcli compose fails with file-read-error on the scalable icon.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* release.yml: the Flatpak job runs without the release environment

It only repackages the Linux job's tarball and signs nothing, so it doesn't need (and
shouldn't hold) the signing secrets while running third-party tooling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-05 04:15:21 -04:00
Brandon ThomasandClaude Opus 5.5 ad86321738 Release 0.2.0; CI builds with --locked (#113)
Bump the workspace version to 0.2.0 (cargo xtask version set 0.2.0; Cargo.lock changes only
the 26 workspace crates). CI's test and clippy steps now pass --locked, as the release packaging
scripts do, so a stale Cargo.lock fails CI instead of the release build.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-05 00:21:13 -04:00
1fe689a7bf docs: add mdBook and security documentation foundation (#35)
* docs: add mdBook and security documentation foundation

* docs: point book links at storytold/photocraft; scope the credits entry

The book's repository/edit URLs and source links pointed at the contributor's
fork. The credits entry now describes what this PR contributed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-04 12:31:18 -04:00
Brandon ThomasandClaude Opus 5.5 1f1ddb2a47 Release pipeline id renames and PhotoCraft branding
- Lowercase reverse-DNS id ai.storyteller.photocraft across icons, Linux
  desktop/metainfo/mime/flatpak, Windows WiX, macOS Info.plist and workflows.
- PhotoCraft display name in the title bar, start screen, web loader,
  Windows exe metadata, README and docs.
- .gitignore target/, roadmap refresh.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 13:25:17 -04:00
Brandon Thomas d98bee4c94 progress, release workflows 2026-10-01 10:49:51 -04:00
Brandon Thomas ae92ac20bf continued wip 2026-09-30 09:37:18 -04:00
Brandon Thomas 16941f7198 one-shot 2026-09-30 05:02:08 -04:00