Merged at the fix-up commit before its main merge (362947695d); the
conflicts with the batch are resolved here: psd_import.rs keeps both
sides' tests, tiff_layers.rs takes ImageData::new.
Picks up the startup prompt without the glyphs a font may lack (storytold/craft-libs#343, #344): the close mark is painted and the version pill is ASCII, so apps that panic on a missing glyph no longer take the prompt down.
Part of tracking issue storytold/craft-libs#319
* feat(updates): startup update prompt modal and direct self-updating
Release checks and the install go through craft-update's Updater: the engine's Help ▸ Check for Updates command keeps its status machine, its transport asks craft-update instead of a private GitHub client, and a found release opens the startup prompt. An install holds back while documents are unsaved, and a finished update closes through the guarded close path. craft-update stays out of the wasm build.
Ref: storytold/craft-libs#319
* fix(updates): address review feedback
Wake the UI when a manual check is queued from a menu callback; the update prompt owns the keyboard while it is open (LightCraft); Download opens through the desktop URL hook (FilmCraft); the Help menu dispatches through menus::invoke (FigCraft); print jobs and comment drafts hold an install back (WordCraft); Check for Updates is a registered command (GridCraft); an install holds back while documents are unsaved.
Conflicts: the batch's wheel reads (#5547 Animated Zoom) keep their extra
argument with main's ids (`Id::unique`, `ui.scope_id()`); crop presets keep
#6620's `apply_choice` with main's accessible name; Bevel & Emboss keeps
#9370's per-mode id with main's name argument; the Stroke field keeps
#7203's gradients with main's name; tone.rs keeps #9139's move of the
Histogram panel to histogram_panel.rs (main only renamed ids there);
translation catalogs as unions.
Not flagged by git, the batch's new code follows egui 0.37 too: dropdowns
take an accessible name (Histogram Channel/Source #9139, tool Mode #5504,
Art History Style #1610, New Snapshot From #3567), MouseWheel events carry
`source`, `WidgetType` is `Role` (ColorButton is ColorWell, #7203),
`Id::new`/`Ui::id` are `Id::unique`/`Ui::scope_id`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* egui037: name each control for what it sets, and order the eager fonts for the UI
CodeRabbit review follow-ups on the migration: the Artboard X/Y fields, the CAF
dropdowns, the channel Solidity field, the GPU backend/adapter dropdowns, the
corner radius fields and the canvas extension colour all had an accessible name
that named another control (or was shared by four). The eager craft-fonts
registration now puts the UI Japanese face first, as the lazy loader does, so
Japanese UI is not drawn in the manifest's Mincho face and shared Han characters
do not take Japanese forms.
Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* egui037: rustfmt the eager craft-font order
---------
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
Opening a 2.14 GB PSD (2000×2000, 109 layers, 821 MB of embedded smart objects) peaked at
7.28 GB and took 6.8 s; it now peaks at 3.00 GB and takes 3.6 s (macOS peak memory footprint,
`cargo run --release -p photocraft-io --example psd_open_mem`).
- photocraft-psd: `PsdFile::from_source` parses from a `ReadAt` source (`FileSource`: pread /
seek_read in bounded reads). Channel data and the merged image become `Stored` ranges read
when decoded; the model equals `from_bytes`'s and writes back the same bytes (tests compare
the two on every generated file, including Lr16/Lr32 layer blocks and truncated files).
- `ChannelData::row_decoder` decodes any band of rows: RLE via its row table (a stored channel
reads only the band's bytes), raw by offset, ZIP inflated once.
- photocraft-io: `import_file` opens a path; PSD/PSB through `from_source`, other formats read
whole. Layers decode in bands of whole tile rows (~32 MB) into tiles: no whole-layer plane or
interleaved buffer. Global blocks move into the document instead of being copied; finding an
embedded smart object no longer copies every file in `lnk2`.
- photocraft-raster: `Surface::share_uniform_tiles` + `TileInterner`; the PSD import shares
single-value tiles (backgrounds, fill caches, masks) across layers.
- The background open job and the CLI/MCP (`automation::files::open`) use `import_file`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with our fix-up branch pr-8885-fixup (8f6863f53) on the author's head 996b55eb4.
Conflicts resolved:
- crates/io/src/tiff_layers.rs: #9148's tiff_source (an empty LMsk gets Photoshop's 14 bytes)
gives way to #8885's tiff_image_source, which writes the same bytes for an empty or short
LMsk; #9148's test stays.
- crates/io/src/flat.rs: #8776's DDS alpha step and #8885's TIFF channel stamp both run.
- crates/io/Cargo.toml, Cargo.lock: #8402's zune dev-deps and #8885's tiff dev-dep.
- crates/io/tests/it/tiff_layers.rs, docs: unions; docs/scorecard.md kept as is, regenerated at
the batch gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psd_import.rs keeps main's warning for an incomplete merged extra channel
next to the PR's Photoshop spot polarity. people.toml takes main's entries
plus jasallen's own. Docs take both sides' status lines and revision rows;
docs/scorecard.md is regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with our fix-up d5139d177 (pr-1199-fixup), which merged main as of 8f09d122d.
Conflicts with main since then and with batch 8:
- photocraft-cli: main split the CLI into clap modules (#7071); the model wiring
(local_headless, configure_cli_models, batch's shared backend, droplet) is re-applied there.
- Cargo.lock regenerated: crates/ml pins ureq =3.4.2, so the app's ureq goes 3.5.0 -> 3.4.2.
- cutout_cmds: main's subject area (#8235) kept for the classical path beside the model path.
- file_cmds batch: main's per-input closure takes `s` later, so the backend and
integrations prefs are read before the closure instead of inside it.
- prefs_ui: model-choice filter with #4573's pref_choice_label; Integrations stays visible
(#4097) with the model choices only where models run; the translation-coverage test main
deleted (c3aa3f9e5) stays deleted; old section_fields test calls take PreferenceFieldOptions.
- Semantic clashes git did not flag: #4097's Integrations test literals in
apps/photocraft take ..Default::default() for the new model fields; main's and #4573's
section_fields test calls pass PreferenceFieldOptions.
- engine lib.rs, prefs.rs, smartselect tests, snapshot example, jobs_ui_tests, people.toml,
docs and i18n catalogs merged as unions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts with #5547 (Animated Zoom) resolved in wheel_nav.rs, canvas.rs and
panels.rs: the Navigator and canvas pass both Animated Zoom and
!animations_off; with Animation Effects off, the sideways part of an
animated wheel zoom pans by the frame's own events folded by their
modifiers (#4573's unsmoothed scroll) instead of the plain-event raw.x,
so Ctrl + wheel still pans sideways there. #4573's modifier test runs
with Animated Zoom off and on.
Repin every craft-libs crate to 2869b8cc. The customized layout is a
craft_ui::docking::Workspace; window.panel.*, window.workspace.* and
ui.dock parse as DockCommand and apply through it. Workspaces saved
before the version field load with their hidden panels, dock-back
places and collapsed groups.
Reconcile main 486afd82cc while preserving reviewed lifecycle, capture, IME and settings migrations. Include canonical dependency pins and the generated lockfile.
All nine CI stages passed: 11,580 passing test executions, zero failures and 91 ignored across 93 summaries. Native/browser acceptance and paired app performance remain open.
Arithmetic-coded frames (SOF9-SOF11) and SOF0/SOF1 frames whose precision
isn't 8 bits go to craft-jpeg; everything else stays on zune-jpeg. 12-bit
files open as 16-bit images. PhotoCraft's own marker scan still supplies
ICC, EXIF, XMP, resolution and Photoshop resources, and damaged or truncated
entropy data adds the Truncated warning. A truncated scan too short for the
frame it declares is refused, as on the zune-jpeg path.
craft-jpeg is pinned to the aiongg fork until it lands on storytold.
Co-authored-by: Michael Higgins <mhiggins217@gmail.com>
Add the man-and-completions example, which writes photocraft-cli's
completions for bash, zsh, fish, elvish and PowerShell (clap_complete)
and its man pages (clap_mangen: photocraft-cli.1 and one per
subcommand, such as photocraft-cli-convert.1) into a directory:
cargo run -p photocraft-cli --example man-and-completions -- <out-dir>
clap_complete and clap_mangen are dev-dependencies, so the binary is
unchanged. clap_mangen gets its env feature, so the pages name each
flag's environment variable as --help does; clap already has env, so
Cargo.lock gains no package for it. The library exposes only
`command()`, the clap definition; the args module stays private.
A test runs Command::debug_assert and checks that every shell's
completions and every page generate. Another repeats the example's two
generate_to calls and pins the exact file list: the five completion
files under the names the packages install, photocraft-cli.1, one page
per subcommand, and no pages for the help subcommand.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Step 5 of the issue: launch_args' hand-written loop, USAGE and
LaunchArgs become a clap derive, which flattens photocraft-automation's
ControlArgs and AutomationArgs. `photocraft --help` comes from the doc
comments; `--version` prints the same `photocraft <long version>` line
(build_info::long_version_static), and `-V` now does too. --control
uses photocraft_automation::args::control_port instead of a local copy.
The cases the loop handled by hand are kept:
- An old macOS Finder's -psn_... argument is dropped before clap,
unless it comes after `--`, where it is a file name.
- The per-process options for the single-instance hand-off come from
ArgMatches::value_source, in a fixed order. A PerProcess enum names
each (and PHOTOCRAFT_GPU_ADAPTER): id() gives a flag's clap id,
Display the flag or the variable, so single_instance's log line reads
as before. As since #7242, a flag's variable counts as the flag: one
clap reads (PHOTOCRAFT_IN_WINDOW_MENUS when it turns the menus on, an
automation root that isn't blank) is counted here, as clap read it;
env_launch_options keeps #7242's rules for the three read elsewhere
(present mode, frame latency, stroke trace) and returns PerProcess.
- A file name that isn't valid Unicode is still reported in a notice
instead of being opened (#1108); file_names returns
FileNames { openable, unreadable }.
- Windows release builds still have no console: help, version and
usage errors show only when redirected, as since #6435. clap's
Error::exit, like print_stdout, never panics on a closed stream.
The variables are now declared on their flags: PHOTOCRAFT_CONTROL_PORT
on --control, the token variables through ControlArgs (so
security::token_inputs, which nothing else used, is removed), and
PHOTOCRAFT_AUTOMATION_*_ROOT added on the desktop's Command only, so
photocraft-cli still ignores them (#1551).
Behaviour changes, as step 0 of the issue decided:
- A bad or missing --present-mode, --frame-latency or --stroke-trace
value is a usage error (exit 2) instead of a warning. Their
variables stay lenient (logged and ignored); presentation's value
checks serve both, and the flags refuse a value with a ValueError.
- A repeated flag is a usage error, and a value can't start with `-`
unless it is a negative number (write --opt=-value).
- clap reports the first usage error, not all of them. On an unknown
argument clap resolves the pending option value but drops its error,
so `--control nope --safegpu` names `--safegpu`.
- An empty or blank variable counts as unset; before, only an empty
PHOTOCRAFT_CONTROL_PORT did, and an empty token or root was passed
on as given. PHOTOCRAFT_IN_WINDOW_MENUS also reads false, no and
off as off (FalseyValueParser). A variable that isn't valid Unicode
where text is expected is a usage error instead of being ignored.
- With --control (or its variable), a token together with a token
file, from flags or variables, is clap's either/or usage error at
parse time, before the server's own check. Without it no token is
read, so a stale exported pair stops nothing, as before.
- With --control given, PHOTOCRAFT_CONTROL_PORT is not read, so a
bad value there is no longer reported too.
The CPU-compatibility relaunch puts --safe-gpu before the original
arguments, so a `--` among them can't turn it into a file name.
The launch_args unit tests now check clap's errors instead of the error
list. The process tests put a late usage error (--control 0 with the
token pair) where they used a bad port, so stderr still shows how the
other arguments were taken, and cover the variables.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Step 4 of the issue: the control token flags and the automation roots
move to photocraft-automation's new args module as two clap structs,
which mcp and serve flatten and the desktop app can flatten in step 5
without moving them again. clap is a plain dependency of the crate.
- ControlArgs holds --control-token and --control-token-file;
AutomationArgs holds the roots, and AutomationArgs::workspace opens
them (replacing the CLI's automation_workspace).
- The port value parser is photocraft_automation::args::control_port,
with its unit tests, for serve --port and --bridge now and the
desktop app's --control later. It ignores surrounding spaces and
refuses anything else with a typed NotAPort.
- --bridge parses to Bridge::Port or Bridge::Addr: it takes a bare
port, meaning 127.0.0.1:<port>, or any value containing a ':', which
the bridge client then validates. Anything else is a usage error
(exit 2) instead of a bridge error.
- 127.0.0.1 addresses are built from Ipv4Addr::LOCALHOST.
mcp_bridge_takes_a_port_or_an_address is new: `--bridge nope` is a
usage error, and `--bridge 1` bridges to 127.0.0.1:1, which a tool
call names when nothing listens there.
No new environment behaviour: the token variables are still read at
run time through security::token_inputs, and the roots stay flag-only.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Replace the hand-rolled parser (USAGE, SUBCOMMANDS, Args::get/has) with
clap derive types in args.rs: a Cli parser, one Args struct per
subcommand, and flattened ExportArgs (convert, run, batch) and
ControlArgs (mcp, serve). The USAGE prose moves into doc comments, so
-h and --help carry it. Handlers keep their bodies; only their argument
type and the lines reading flags change.
Behaviour is kept: the last of a repeated flag wins (args_override_self),
values stay strings checked by the handlers with the same messages and
exit 1, a value flag never takes the next flag as its value, and help
there (`--out --help`) is still help, --version/version are checked
before clap, help goes to stdout with exit 0 and usage errors to stderr
with exit 2. run pairs each --params with the --cmd before it by putting
both back in command-line order with ArgMatches::indices_of.
mcp and serve keep #8035's launch checks: no positional arguments, no
repeated or empty options, --control-token or --control-token-file but
not both, a token flag only with --bridge or --port, and no automation
roots with --bridge. clap gives the first four (a repeat is found by
scanning the arguments while args_override_self stays on); a small
check after parsing gives the last two, with #8035's messages.
Behaviour changes (allowed by the issue's step 2):
- Help and error text is clap's: per-subcommand help instead of the one
USAGE page, "unexpected argument '--fromat' found" instead of
"unknown flag --fromat for batch", and so on.
- A wrong positional count is a usage error (exit 2) instead of a
handler error (exit 1): convert with one or three paths, info with
none, droplet with no inputs, run with two files.
- A positional starting with '-' (other than "-") needs a '--' before
it, and '--' now ends the flags instead of being an unknown flag.
- A value starting with a single '-' is written --out=-image.png (or
./-image.png), as one starting with '--' already was; --quality and
--filter keep negative numbers (allow_negative_numbers).
- `help <subcommand>` prints that subcommand's help.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every failure was formatted into a String where it happened, so callers
could only print it. Subcommands now return a CliError, the actions
loaders an ActionsError and the hand-written parser a UsageError; each
variant prints exactly the text the String did, and the engine and
automation errors pass through unchanged.
Also: print_json takes a JsonStyle instead of a bool, the engine's
missing_font_warnings replaces the CLI's copy (same text), batch warns
through warn_all, run drops a second, no-op insert into its reported
font set, and the src tests share one Invoke helper.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings in 412 commits from main (cf9ff2a2). Two conflicts:
- crates/ui-egui/src/lib.rs: main added updates_ui::tick right after
prefs_ui::tick, where this branch calls wheel_nav::unsmooth. Both stay,
main's first; the update tick reads no input.
- Cargo.lock: main's, with cargo adding back this branch's one line (the
desktop app's direct windows dependency).
docs/scorecard.md is regenerated; it differs from main only in UI-217-10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in 72 commits from main (bde13687). Two conflicts:
- apps/photocraft-web/Cargo.toml: main added the web-sys RequestMode feature;
the list is main's plus this branch's MediaQueryList.
- crates/ui-egui/src/prefs_ui.rs: main deleted the translation-coverage test
preference_labels_are_translated (c3aa3f9e, missing translations are now
reported by xtask ci); this branch had only adjusted it, so it goes.
docs/scorecard.md is regenerated; it differs from main only in UI-217-10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings split/tiff-extra-samples-codecs at e6fca054, which merged main
1695f323. Conflicts:
- crates/io/src/lib.rs: main moved PSD import into import_psd(); it now
passes PsdImportOptions::default().
- PsdExportOptions (psd_export.rs, tiff_layers.rs): main's new
read_linked_files next to this branch's photoshop_spot_polarity.
- docs: main's newer status lines and rows, with this branch's TIFF
statements re-applied (dated 2026-10-23). docs/scorecard.md is main's
and is regenerated after PR 2 is merged too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tilt-shift reference tests and tiled_clear_and_fill_match_one_pass_over_the_area cross pixel
formats or channel counts with independent geometry and fill settings. Each run takes a quarter of
the combinations through Sweep, a different quarter each UTC day.
precheck_agrees_with_execute built all five document states for every call and kept one; it now
builds only the state the call needs.
swept_blocks_match_the_pixel_search skips the cases where the colour mode reaches extrude only
through a channel count and ink setting an earlier mode already covered. It and
damaged_layer_data_opens_flattened run a quarter of their cases per run, a different quarter each
UTC day (photocraft-testkit's Sweep). PHOTOCRAFT_FULL_SWEEP=1 runs every case, and
PHOTOCRAFT_SWEEP_PART=<n> replays the part a failing run printed.