Commit Graph
5851 Commits
Author SHA1 Message Date
vangel76andClaude Sonnet 5.5 dc791a43de raw: develop the default rendering band by band without the f32 RGB frame
When neither Dehaze, a local tone slider nor a camera black point is in play, no
statistic of the whole frame is needed, so the demosaic output no longer has to be
kept. Every band is demosaiced into a small band buffer and encoded straight into the
u16 output. The f32 RGB frame (12 bytes a pixel, 545 MB at 45 MP) and the pass that
read it back are gone. A Bayer sensor also skips the padded CFA plane: each band
normalizes the rows it reads (with the mirrored borders) into a small window. X-Trans
keeps its plane and green plane, LinearRaw has none. Tone, Dehaze and black points
keep the full-frame path.

The bands are the ones the full-frame path computes, with the same arithmetic per
pixel, so the output is bit-identical (tests compare both paths for Bayer, X-Trans and
LinearRaw, odd sizes, band borders, orientations and 1, 3 and 16 threads).
working_memory now charges what each path really holds.

Fixes #9434

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:35:29 +01:00
vangel76 c7c5aae4af Merge remote-tracking branch 'origin/perf/9428-raw-encode-stage' into perf/9434-fused-develop 2026-10-25 14:14:18 +01:00
vangel76 0966e14ae0 Merge origin/perf/9427-ahd-demosaic (keep clampf from the R1 fix) 2026-10-25 14:14:18 +01:00
vangel76andClaude Sonnet 5.5 df23873ccf raw: block-wise final encode stage for the default rendering
When neither Dehaze nor a local tone map is active (neutral tone and no
black point; only the Nikon D4 profile sets one), the matrix is applied
into a block of 768 samples and the gamma encode runs over the block:
clamp and square root first (vectorises), then the table with
[value, step] pairs. Same arithmetic, same output; other renderings keep
the old per-sample loop. A test compares the block encode with the
per-sample encode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:05:24 +01:00
vangel76andClaude Sonnet 5.5 d9aed18582 raw: AHD demosaic with reused planar scratch, about 1.6x fewer instructions
Same arithmetic in the same order, so the output is bit for bit the same
(the old code stays under cfg(test) as ahd_ref and a test compares them).
Scratch planes are allocated once per group of bands instead of per band
(no per-band zeroing or page faults) and never cleared, since every cell
a stage reads was written earlier in the same band. The layout is planar
RGB/Lab, the candidate pixel is built from values instead of indexed
stores (which stalled store forwarding), the 3x3 homogeneity sum is
separable, and the LabF table holds [value, step] pairs. The 32-row band
is kept, as a larger band costs peak memory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:01:56 +01:00
vangel76andClaude Sonnet 5.5 97a719a7a7 raw: a non-finite black level no longer panics the AHD demosaic
A DNG whose BlackLevel is a DOUBLE beyond f32 reads as inf. The periodic
fill then computed (raw - inf) * 0 = NaN, and AHD's f32::clamp with NaN
bounds asserts. The fill now returns (0, 0) for a non-finite black, as
Scale::value does, and AHD uses a compare-and-select clamp that gives the
same result for every non-NaN bound.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 13:58:12 +01:00
aiongg b0622d20ca Autosave: a recovery listing judges each key once, so an autosave in progress isn't listed twice (#9324)
* Autosave: a recovery listing judges each key once, so an autosave in progress isn't listed twice

* Autosave: the recovery listing builds for the browser too
2026-10-25 01:09:21 -04:00
aiongg c65b5d25d3 Merge pull request #9061 from wanikhawar/wanikhawar/modal-workspaces
fix(ui): Liquify, Content-Aware Fill, Filter Gallery and Adaptive Wide Angle are modal, like Camera Raw
2026-10-24 22:11:26 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 b278ff7245 security(affinity): charge archive names, failed reads, corner arrays, attachments, pages and font names (#9257)
* security(affinity): charge archive names, failed reads, corner arrays, attachments, pages and font names

Bound the remaining per-occurrence and table-driven expansion work left after
#8938: aggregate archive entry-name bytes (copied only for the winning name),
extraction allowance charged before decoding so failed checksums are not free,
CnrD corner arrays charged as curve records, AdCh and gradient arrays charged
as slots (gradient positions copied only up to the stop limit), a document-wide
page-rectangle cap, and per-field and aggregate limits on retained font names.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* security(affinity): charge pixel state-array scans against the slot budget

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(affinity): charge decoded name length and bound PagR rectangle collection

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 21:20:41 -04:00
Khawar Zamman Wani 874ff42a9e docs(control): Adaptive Wide Angle is a modal workspace too 2026-10-25 06:49:07 +05:30
Khawar Zamman Wani 154f7b377e Merge pull request #1 from aiongg/modal-wide-angle-9061
Adaptive Wide Angle is modal too
2026-10-25 06:17:37 +05:30
aiongg b4a297fe99 Merge pull request #9195 from aiongg/craft-jpeg-adopt
JPEG: open 12-bit and arithmetic-coded files through craft-jpeg (#4934, #5003)
2026-10-24 20:45:42 -04:00
aiongg 3d0c176e80 JPEG: a craft-jpeg frame without scan data is refused before decoding 2026-10-25 00:00:01 +00:00
aiongg 703bc4c821 raster: a translated surface shares one rebuilt tile however many threads build it (#9230)
Workers that missed the shared-tile memo at the same time each built
and kept a copy, so the number of copies grew with the thread count and
translated_shares_tiles_rebuilt_from_shared_sources failed on machines
with 16 or more threads. Each worker now takes the tile stored first.
2026-10-24 19:49:00 -04:00
aiongg ae368d5a61 JPEG: lossless and 12-bit progressive files are unsupported, not damaged 2026-10-24 23:20:40 +00:00
aiongg 48166f6ab3 Pin craft-jpeg to storytold craft-libs 6e7f285 2026-10-24 22:28:39 +00:00
aiongg 4198b9fa3b JPEG: follow craft-jpeg's review fixes (non_exhaustive Limits) 2026-10-24 22:06:38 +00:00
aiongg 486afd82cc Merge pull request #8994 from FinnNk/fix/cpu-canvas-centred-previews
fix(ui): centre size-changing filter previews on the CPU canvas
2026-10-24 17:56:16 -04:00
bfentyandbfenty 155d1eac36 ui: highlight menu rows while dragging with the button held (#9112)
Response::hovered is false while the primary button is down, so a
press-drag through a menu never moved the highlight and it desynced
from the cursor. Hit-test with contains_pointer, which keeps answering
while a button is held.

Closes #8887

Co-authored-by: bfenty <1398236+bfenty@users.noreply.github.com>
2026-10-24 17:55:20 -04:00
denstu473 fa253cb372 fix(ui): keep repeated Tab presses in text editing (#8889) (#9030)
egui queues focus navigation before the type tool consumes Tab. Removing the
key event leaves that navigation queued, allowing menu focus to block later
Tabs and ordinary text input. Cancel the queued move when inserting a tab.

Add a full-app input regression that fails on the second Tab before the fix
and verifies three Tab presses, caret positions, and subsequent typing.
2026-10-24 17:54:55 -04:00
aiongg be82b40e25 Merge pull request #9194 from 1Git2Clone/fix/cli-review-round
fix(cli): help after a mistake, and mcp/serve option notes in --help (#7071)
2026-10-24 17:53:00 -04:00
aiongg 16d355def4 Adaptive Wide Angle blocks other menu commands like the other full-window workspaces 2026-10-24 21:31:56 +00:00
aionggandMichael Higgins 7cf4e6eca0 JPEG: open 12-bit and arithmetic-coded files through craft-jpeg (#4934, #5003)
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>
2026-10-24 21:19:48 +00:00
IvanandClaude Opus 5.5 9c56f5b9ca refactor(cli): type the mcp/serve option refusals (#7071)
The checks parse() runs after clap built their messages from a const
and two format! calls. ServerOptionError holds them as thiserror
variants beside ValueError, each with the clap ErrorKind it reports
as: the PHOTOCRAFT_CONTROL_PORT host:port refusal, bridge roots, and a
token flag without its transport. The output is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 00:16:23 +03:00
xsvm 6719f1813e Segmentation: anti-aliasing the full-resolution mask skips runs far from its edge (#8554)
* Segmentation: anti-aliasing the full-resolution mask skips runs far from its edge

Select Subject, Object Selection and Select Sky finish with antialias_u8
over the whole full-resolution mask, which read nine neighbours through a
bounds-checked closure for every pixel: 171-185 ms of an 800 ms first
Select Subject on 24 MP (ubuntu-latest, 4 cores). A run of 64 pixels whose
3x3 neighbourhood holds one value has no edge pixel, so it is only compared
now; other runs take the per-pixel rule as before. The output is byte for
byte the same, checked against the per-pixel rule over widths around the run
length, single rows and columns, discs, noise and stripes. Part of #211.

* Segmentation: the mask's anti-aliasing and its test share one per-pixel rule

The 3x3 rule moves into edge_value, which antialias_u8 calls for spans that
are not uniform and the test calls for every pixel, so the test checks only
that skipping never misses an edge pixel instead of keeping a second copy of
the rule. A small case pins the rule's values.
2026-10-24 17:13:24 -04:00
IvanandClaude Opus 5.5 487e18d726 fix(cli): help after a mistake, and mcp/serve option notes in --help (#7071)
The review round of the #7071 stack, which #9167 merged without
(it took the pre-review heads):

- A bare --help or -h before `--` is help after any parse error, not
  only after a missing value: `mcp stray.psd --help` and
  `serve --port abc --port 1 -h` print the subcommand's help.
- mcp and serve --help carry #8035's rules: what the automation roots
  grant, that bridge roots are refused, and that the token flags need
  --bridge or --port (or PHOTOCRAFT_CONTROL_PORT).
- check_server takes the subcommand and whether a transport is set;
  the bridge-roots check moves next to the other mcp checks.
- The server tests match clap's "a value is required for" text, and
  mcp.md in the book and docs names PHOTOCRAFT_CONTROL_PORT.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 00:08:14 +03:00
aiongg 14a4ab9b3f Merge pull request #9167 from aiongg/cli-batch-7071
CLI integration batch: clap for photocraft-cli and the desktop app (#7071)
2026-10-24 14:31:21 -04:00
aiongg 8d84225603 Merge pull request #9161 from davidmann4/fix/linked-embed-decodable
PSD export: embed a linked file only when it is an image PhotoCraft can open
2026-10-24 14:06:49 -04:00
aiongg 1a93824602 Packaging: --skip-build without the man-and-completions example leaves the CLI docs out
Prebuilt binaries packaged with --skip-build failed when the example
wasn't built with them. The package now leaves photocraft-cli's man pages
and completions out with a warning, as a cross build without an emulator
does.
2026-10-24 18:02:12 +00:00
David MannandClaude Opus 5.5 e3a8dc564a Tests: the workspace PSD-save test links a real image
PSD export now writes a link to a file it can't open as pixels, so the
test's trusted-export check embedded nothing. Linking a PNG keeps that
check meaningful: the trusted export carries the file's exact bytes and
the workspace save doesn't.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 19:41:11 +02:00
aiongg 8950052c57 Packaging: build the man-and-completions example in its own cargo run
Built in the same run as the release binaries, the example's
dev-dependencies would have their features unified into them.
2026-10-24 17:27:54 +00:00
aiongg ae75ef33bb CLI batch: fmt, and valid values in the server option repeat cases
A typed --bridge or --port value is parsed before clap sees the repeat,
so "one" reported an invalid value instead of the repeated option.
2026-10-24 17:15:58 +00:00
David MannandClaude Opus 5.5 cd047a3ddc Package: copy a linked file only when it is an image PhotoCraft can open
File › Package copied every linked file into Links and only then
relinked the packaged document, so a link to a file that can't be
placed left its copy behind and the package failed. Such a link is now
listed as missing and not copied, like the PSD export rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 19:12:31 +02:00
David MannandClaude Opus 5.5 e279d5c824 PSD export: keep linked and embedded copies of the same contents apart in the cache
Duplicates share a contents identity, so a linked and an embedded copy
of the same bytes shared one cached conversion: whichever was written
first decided for both. The cache key now includes whether the bytes
came from a link.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 18:58:09 +02:00
IvanandClaude Opus 5.5 8544c98517 docs(cli): installing and generating completions and man pages (#7071)
Say which packages install them and how to generate and install them
by hand from a checkout. The desktop app has neither.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 cb730955f6 feat(freebsd): ship photocraft-cli's man pages and completions (#7071)
The FreeBSD tarball carries them in the ports layout: man/man1,
share/bash-completion/completions, share/zsh/site-functions and
share/fish/vendor_completions.d.

The man-and-completions example is built in the same cargo build as
the binaries (--bins --example), and package.sh runs it from the
target dir. With --skip-build it must already be built, or the script
says so and fails. A dry run builds nothing, so it leaves them out.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 92863642a7 feat(linux): ship photocraft-cli's man pages and completions (#7071)
The deb, rpm and tarball install the man pages in /usr/share/man/man1
and the bash, zsh and fish completions in bash-completion's
completions, /usr/share/zsh/site-functions and fish's
vendor_completions.d. Fedora's and openSUSE's zsh read site-functions;
Debian's reads /usr/share/zsh/vendor-completions instead (its zsh is
configured with --enable-additional-fpath), so the deb also carries a
symlink there.

The man-and-completions example is built in the same cargo build as
the binaries (--bins --example, same profile, features and target),
and package.sh runs it from the target dir. A cross build runs it
through $EMULATOR, as the --version smoke test does, and without one
packages no pages or completions, with a warning. With --skip-build the
example must already be built, or the script says so and fails. The
pages aren't gzipped, like the other packaged docs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 69d277440d feat(cli): generate man pages and shell completions (#7071)
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>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 e96b5e2b11 docs(app): describe the desktop flags' variables and usage errors (#7071)
Say that `photocraft --help` lists the flags and their variables, that
a flag wins and an empty variable is unset, and what is a usage error;
that a bad --present-mode or --frame-latency is one while a bad
variable is still ignored; list PHOTOCRAFT_IN_WINDOW_MENUS; and that a
token with a token file is a usage error for the control server too.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 0d5b7f6b39 refactor(app): parse the desktop command line with clap (#7071)
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>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 483b01b562 test(app): pin --version and the automation root variables (#7071)
Two process tests of today's behaviour that the clap port keeps:
`--version` prints `photocraft <long version>` and stops reading the
arguments, and PHOTOCRAFT_AUTOMATION_READ_ROOT / _WRITE_ROOT reach the
control server's workspace like their flags. The process tests return
a Run struct instead of tuples, and control_exit_code gains run_with
to set variables.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 2bba1c13fe refactor(app): move the launch argument parser into launch_args (#7071)
The hand-written argument loop, its LaunchArgs, USAGE and helpers
(parse_control_port, usage_exit_code, print_stdout) and their unit
tests move unchanged from main.rs into a new launch_args module; only
the module doc, the `mod`/`use` lines and pub(crate) visibility are
new. main.rs keeps the environment defaults and the exits.

This is the step before the clap port, so that one reads as a rewrite
of launch_args.rs rather than a large main.rs deletion. Check with
`git show --color-moved=dimmed-zebra`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:27 +03:00
IvanandClaude Opus 5.5 23548ee92a fix(cli): check the token pair only where a token is read (#7071)
With the environment fallbacks, clap's conflicts_with on the token pair
made a stale exported PHOTOCRAFT_CONTROL_TOKEN and
PHOTOCRAFT_CONTROL_TOKEN_FILE a usage error for every mcp and serve, though
headless mcp and stdio serve read no token. As on main, the pair is now
refused only by serve on TCP and mcp bridging, still as clap's either/or
usage error naming each half as it was given.

An automation root on the command line with PHOTOCRAFT_CONTROL_PORT set
is a usage error too: the variable would switch mcp to bridging, which
drops the roots, with only a note on stderr.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 b4949702e2 docs(cli): describe the mcp and serve environment fallbacks (#7071)
Say that PHOTOCRAFT_CONTROL_PORT is the fallback for serve --port and
mcp --bridge (with the stderr notice and the empty-is-unset escape),
and list the CLI's variables on the book's CLI page, which also says
the automation root variables stay the desktop app's.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
18f9f11de4 feat(cli): environment fallbacks for mcp and serve (#2491, #7071)
The desktop app's --control already reads PHOTOCRAFT_CONTROL_PORT, and
the CLI read the token variables at run time. Now every fallback is
declared with `env =` on its flag (clap's `env` workspace feature, no
new crate in Cargo.lock):

- --control-token and --control-token-file in ControlArgs fall back to
  PHOTOCRAFT_CONTROL_TOKEN and PHOTOCRAFT_CONTROL_TOKEN_FILE, as
  security::token_inputs did at run time. The CLI no longer calls
  token_inputs, so the variables are read in one place; the desktop
  app still uses it until step 5. Help names the token's variable but
  never shows its value (hide_env_values). A token from the
  environment together with the other flag is now clap's either/or
  usage error (exit 2) rather than the automation crate's (exit 1).
- `serve` takes PHOTOCRAFT_CONTROL_PORT as --port and `mcp` as the
  bridge port when the flag is absent, as #2495 proposed. The variable
  must be a port: `mcp` refuses 127.0.0.1:7878 or :7878 from it, as the
  desktop app's --control would; only --bridge takes host:port.
- The automation roots stay flag-only for the CLI: a
  PHOTOCRAFT_AUTOMATION_*_ROOT exported for the desktop app must not
  widen headless file access (#1551).
- A flag wins, and an empty or blank variable counts as unset
  (blank_env_is_unset; clap would hand the empty value to the parser),
  so stdio and headless stay the default.
- A bad value or conflict from a variable is a usage error naming the
  variable instead of the flag it stands in for (name_env_vars).
- The port variable changes what the command does, so when it (not the
  flag) turns on TCP or bridging, the command says so on stderr and
  names the way back (ArgMatches::value_source tells which).

bin() in the tests removes the token and port variables, so an
exported one can't change a test. New tests:
control_port_env_switches_modes_with_a_notice_and_flags_win (ported
from #2495, also checking the bad values) and
control_token_env_vars_are_the_flags_fallbacks.

Co-authored-by: ahmedhosnypro <ahhosnyas@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 2f13a1564c refactor(cli): share ControlArgs and AutomationArgs, typed --bridge (#7071)
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>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 7cb986a02d docs(cli): describe the typed value and either/or checks (#7071)
Name what typed values made a usage error: a value out of range
(--quality 0), malformed --new or --params JSON, <FILE> together with
--new, and --control-token together with --control-token-file. A path
that is not valid Unicode now reaches the command: convert takes it,
info, run and batch fail through the automation layer (exit 1), and
droplet, which takes text, refuses it as a usage error.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 f540d3f5e3 refactor(cli): parse photocraft-cli values into types at the boundary (#7071)
Step 3 of the issue: handlers get values that are already checked, so
a bad one is a clap usage error naming its flag (exit 2) and the
"bad --x" errors (exit 1) are gone.

- --quality is a u8 in 1..=100; --tga-bits, --dpx-bits and
  --dds-compression parse to photocraft_io's TgaBits, DpxBits and
  DdsCompression through their from_bits/from_name, and --tiff-compression
  and --jpeg-format to photocraft_codecs' TiffCompression and JpegFormat,
  refusing a bad one with a typed ValueError; serve --port is a u16. export_opts becomes `impl From<&ExportArgs> for ExportOptions`,
  which can't fail.
- run's --new and --params parse to serde_json::Value, so JSON that
  doesn't parse is a usage error; whether it is an object is still the
  command's to say (exit 1). The --cmd/--params pairing keeps using
  ArgMatches::indices_of, now over typed Step values.
- Paths are PathBufs (convert, info, run, batch, the control token
  file and automation roots), and run hands the OsStrings to clap
  instead of refusing non-Unicode up front: a Latin-1 file name works
  as a path (#1108), and one given as text is clap's usage error.
  load_actions_file takes a &Path, and photocraft_engine's same_file
  takes any AsRef<Path>, so convert's #7437 check compares the PathBufs.
- Rules live in the types: run needs exactly one of <FILE> or --new
  (ArgGroup "source") and a --cmd or --out ("work"); batch's --actions,
  --in and --out are required; --control-token conflicts with
  --control-token-file. The runtime checks and their messages are
  deleted. The automation crate's own token check stays, as it also
  sees PHOTOCRAFT_CONTROL_TOKEN(_FILE).

Tests changed for this: run_errors, quality_outside_1_to_100_is_refused,
dpx_converts_both_ways_with_the_bits_asked_for,
dds_converts_both_ways_with_the_compression_asked_for,
tga_bits_choose_the_targa_depth, jpeg_format_chooses_the_scans
(exit 2, the error names the flag) and
the #1108 test, now
a_non_unicode_path_is_taken_and_non_unicode_text_is_a_usage_error:
convert reads and writes such a file name end to end, while info opens
through the automation layer, whose paths must still be UTF-8, and
refuses it (exit 1). either_or_flags_are_usage_errors is new.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 a6a82b0dca docs(cli): describe repeated flags, dash values and --version (#7071)
The CLI page said parsing reports malformed flags and JSON. Name what
the step 0 decisions made a usage error: a single-value flag given twice
(--cmd and --params repeat), how to pass a value starting with '-', and
that `version` is only --version/-V. Say why --quality allows negative
numbers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00
IvanandClaude Opus 5.5 ef5688fec3 refactor(cli): one run taking any OsString arguments (#7071)
run took &[String] and run_os &[OsString], and run_os only checked
each argument was Unicode before calling run. A single run generic over
Into<OsString> (as clap's try_get_matches_from is) does that check once
and serves main's args_os and the tests' &str slices alike.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:45:26 +03:00