Conflicts resolved as unions of both sides: codecs re-exports (PngRows + TiffBands), smart_cmds source imports
(main's source_surface_from_buffer and cache API with #8571's svg_source_image_in/convert_svg_source; main's
duplicate rendering import dropped), io flat.rs test modules (#8536's banded TIFF tests and main's PNG ones),
menus/invoke.rs (#8571's smart SVG intercept and main's layer.selectUnderCursor), state.rs (#8311's
type_box_overflow and main's type_insertion_style), the canvas_geom test keeps the is_finite guard, docs
status lines keep both change lists, i18n catalogs keep both sides' rows (hi.tsv: main's wording where both
added the same string).
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#2f7bf5, its colour in ArtCraft Launcher). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).
craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- apps/photocraft/src/app_dirs.rs
- crates/codecs/src/codecs/via_image.rs
- crates/engine/src/canvas_geom/tests.rs
- crates/ui-egui/src/panels/blend_preview.rs
Main's noop_step now takes the step's params (an Arrange that cannot move the layer is a no-op too); it is public for the CLI's run and batch, which pass them.
- Smart-object contents (Replace Contents, Convert to Layers, Stack Mode,
internal Edit Contents) refused any SVG whose canvas area times its
node count passed 24 MP, e.g. a 1920x1080 layout with 12 shapes.
decode_source now uses the bounded image parse with File > Open's
layered import (import_svg_bounded_images). Convert to Layers' shape
cache cap is 1 GiB in the document's format, the import's own budget,
not 24 MP (four full-canvas shapes in a 4K document failed).
- The bounded resolver failed the whole drawing on an embedded image it
can't read (an unknown MIME type, a corrupt PNG); it now leaves it out
as usvg does, so such SVGs place and render again. Byte and decode
overruns stay errors.
- resvg gives each embedded SVG image a scratch pixmap the size of its
target and frees it after, so the scratch budget counts the deepest
chain of nested SVG images, not every sibling: 12 SVG icons on a 2 MP
canvas, or one at a 12 MP render, no longer fail.
- Main's shared nesting budget (#9397) now covers the bounded path too:
511 + 511 levels across a drawing and its embedded SVG overflowed a
1 MB stack.
- An AI working copy is regenerated through VectorCraft only when
VectorCraft is the editor; Illustrator or another program gets the
original bytes. VectorCraft is recognised by bundle id, a known
install, or its name (vectorcraft[.exe], vectorcraft-<version> AppImage,
ai.storyteller.vectorcraft).
- updateSvg refuses an AI/PDF/EPS source (updateVectorSource saves it
back in its own format); the doc no longer calls updateSvg "existing".
- Edit Contents on contents already open in the internal editor switches
to that document, as main does, instead of an error.
- Stack Mode on an SVG refuses up front when one float frame per layer
would pass 1 GiB (the layered import no longer refuses for it).
- A stored display preview still fails on an embedded image it can't
read (strict budget, its own import_svg_preview_with_work; external-
editor saves and conversions leave the image out as placement does); skipped embedded SVGs still use up the XML byte
budget; an image under an unknown MIME type or with no pixels is left
out uncharged.
- Merge: the PSD import reads a preview's native source from the
document's global blocks; main's owned open moves them out of the
file, so every stored preview was dropped.
- Main's strict Clippy rules on the PR's app code (explicit import, a
Unix-only test helper, one long opt-in test).
Co-authored-by: Mocky Digital <282319807+mockydevs@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: svg.rs (main's bufread inflate and shared render helper, kept
with the PR's bounded raster path), psd_import.rs (the PR's vector
preview moved into main's smart_content), menus/invoke.rs (the SVG
intercept moved into main's split invoke_unguarded), rendering.rs and
the ui-egui import lists (main's explicit imports), Cargo.lock, docs
status lines (unions).
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Integrate lossless export-only JXL and independent decoder regression matrix, preserving current JPEG/TIFF save extensions and 12-bit/arithmetic JPEG decoder dependency.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Prebound the listener inside current authorized control_setup before Linux display and GPU initialization, retaining integration preferences, workspace grants, authentication and single-instance routing. Preserved actual port logging, added occupied-port/prebound-dispatch regressions and bounded child cleanup to current process test harness.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Register starter actions and bounded background-blur command in current modules; preserve persistent migration marker and portable JSON defaults compatibility.
serve_fonts asks for the interface language's served script font once per
language, but the font manifest loads asynchronously: when the first frame
ran before it arrived, no fallback was registered yet, the request found
nothing, and an Arabic interface on an English browser stayed without its
font until the language changed. The manifest load now asks for the
interface language's font along with the browser languages'.
Review finding on #10006.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web build has no system fonts and embeds no craft fonts, so an Arabic
(or Japanese, Chinese, Korean, Hindi) interface had no font for its labels:
served fonts were registered only in the document text engine. A served
script fallback now also joins the end of every egui font family, and the
font stack theme::install_fonts rebuilds on a language change includes it
again. The web shell asks for the interface language's script font, not
only the browser languages'.
Review finding on #10006.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now carries #9031 (Halftone) and #8923; this branch keeps only what it adds on top: the guard for a setting beside a Gallery stack's effect list, its test, and the PSD open warning for patterns that paint nothing.
Integration batch 9 (#9775) raised the preference count to 181 and
added its own 2026-10-27 status lines and history rows to gaps.md and
ui-parity.md. Both sides are kept: the scorecard regenerates to 11 of
181 do nothing, the branch's entries say 181 and are dated 2026-10-27,
above main's 12-of-181 rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document windows (Window › New Window), Camera Raw and the screen color
picker were built from ViewportBuilder::default() with no app id, so on
Wayland their class was empty: Hyprland rules couldn't match them and docks
couldn't map them to the .desktop file. They now use the same app id as the
main window, defined once in photocraft_ui_egui::os_window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a pen attached, or Preferences › Performance › Linux display server set
to X11, the window opens through Xwayland on a Wayland desktop. The picker
followed the window's display server and grabbed Xwayland's root window,
which holds no desktop pixels: GetImage failed with BadMatch. Choose the
backend from the desktop session instead, so Wayland always uses the
portal's PickColor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the export speed work (#9877) and the rest of main. Main's
prefs audit now counts 180 preferences, so the regenerated scorecard
reads 11 of 180 do nothing, and the branch's doc entries say 180 to
match; the 2026-10-25 history rows keep their 178.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #9775: the Linux portal swaps the extension for the picked type, but macOS and rfd keep the suggested name, so an opened image.pgm could still be saved as image.pgm holding P6 data. The suggested name itself now takes .pnm for any Netpbm subtype; the services test fails without it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts with main's batch #9880:
- psd file.rs / layer.rs: #5287 (PSB by encoded size, RLE tables widened on
promotion) against #6583's lazy reader. Main's new widening and length code
read `.data`, which #6583 leaves empty while the bytes stay in the source;
it now goes through `encoded()` / `encoded_len()` (also the auto-merged
lines in `rle_widening`, `encoded_len` and `LayerRecord::write`), and the
lazy reader sets `write_as_psb: false`. New test
`a_lazy_psd_promotes_to_the_same_psb_as_an_eager_one`: a lazily read PSD
writes the same PSB bytes as an eager one; it fails with a `.data` read.
- direct_select.rs / vector_ui.rs: #9321's Type Path and #6353's hidden
Work Path both kept.
- io tests main.rs: both modules. scorecard.md regenerated (12 of 181);
ui-parity.md and gaps.md follow it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CodeRabbit on #9775: save_filters adds an unlisted suggested extension as its own type, so Save As on an opened .pgm offered .pgm again, though the encoder writes P6 for an RGB document. A suggested .pbm/.pgm/.ppm/.pam/.pfm now starts on the Netpbm type as .pnm; the services test checks every subtype name and fails without this.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main split crates/engine/src/prefs.rs into prefs/ and moved the
gpu_startup tests to gpu_startup/tests.rs: read_section now sits beside
read_lenient in prefs/store.rs, and the malformed-sibling assertions
moved with the tests. Doc status lines and revision rows keep both sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #9775: the encoder picks P5/P6/P7/PFM from the image, not the name, so a .pbm, .pgm or .pfm picked in Save As held other data (an RGB document as P6 in a .pbm). Only the neutral .pnm is offered; File > Open still reads every Netpbm extension.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main reorganised the source tree (command modules under engine/src/cmd, tools and dialogs under ui-egui/src, large files split into modules, inline test modules moved to their own files). This branch's changes to the moved and split files are ported to their new locations.
Picking Netpbm for a bare name (Untitled) took the row's first
extension, .pbm, though the codec writes P6/P7/PF from the image, never
a 1-bit PBM: an RGB document saved as x.pbm holding P6 data. The row now
starts with .pnm. File › Open didn't list .pnm or the new .dib alias, so
a file Save As had just written was hidden in the Open dialog; both are
now in All Formats and their own type. The test checks every new type
and alias is offered, the Netpbm row's first extension, that Open lists
every extension Save As writes, and what Save As writes to .pnm (P6) and
.hdr (#?RADIANCE), as asked in review.
Co-authored-by: buivanvu94 <125793064+buivanvu94@users.noreply.github.com>
Co-authored-by: tanish2k09 <13985398+tanish2k09@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the working file changed between settling and reading, the watcher
sent "The SVG is still being saved" to the UI as an error. Treat that
result as a retry: clear the pending stamp so the new save settles again,
and send nothing. Other read errors are still reported once.
Tests cover the retry, single reporting of other errors, queue
backpressure and receiver drop, and a stress run of atomic replacements
that must deliver only complete saves ending with the final one.
Resolve conflicts with the engine's cmd/ module move and the per-crate
integration test binaries. Run a selected VectorCraft.app bundle directly
(identified by bundle id) and reject nested gzip in bounded SVG parsing.
- External vector editing on macOS: discover VectorCraft.app in /Applications and
~/Applications, offer Illustrator when an Adobe Illustrator*.app is found, and accept a
chosen .app under Other application. CFBundleExecutable is read from a bounded Info.plist
(XML or binary, streamed with a nesting cap) and must resolve inside Contents/MacOS;
chosen bundles open with /usr/bin/open -a. vectorcraft-cli is looked for in the same
bundle, then in known installs. Logic is compiled and unit-tested on every Unix.
- Windows "not found" errors name the dialog's "Other application" option.
- Native preview hash verification is remembered per source allocation in a 32-entry
Weak-keyed LRU (no strong references, no reusable stale addresses, lock not held
while hashing), so refreshes and warp frames hash a shared source once.
- source_box uses the new io::svg::bounded_canvas_bounds for SVG without Stack Mode, so
warp setup gets a frame for drawings whose editable layer caches exceed the budget.
Stack Mode and Edit Contents stay strict. Exact 24,000,000-pixel boundary tested.
- Document every non-test function this PR adds; describe macOS behaviour in
docs/embedded-vector-editing.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>