The app still showed the Adobe Photoshop name in several user-visible
strings. Per the product rule (user-facing text says PhotoCraft only),
none of these mention Photoshop now.
UI labels, tooltips and status messages:
- New Document preset "Default Photoshop Size" -> "Default PhotoCraft Size"
- "All shortcuts reset to Photoshop defaults." -> "All shortcuts reset to
PhotoCraft defaults."
- "Import Photoshop brushes (.abr)" -> "Import brushes (.abr)"
- smart-filter fallback name "Photoshop filter" -> "Smart filter"
- File > Save As .psd/.psb filter "Photoshop" -> "PSD Document"
Errors and warnings:
- "this Photoshop filter isn't implemented in PhotoCraft" -> "this smart
filter isn't implemented in PhotoCraft"
- smart_map: "has no PSD equivalent", "not a recognized filter", "its
filter data is missing/unreadable", "this filter has no PSD equivalent"
- tiff_layers: "the PSD layer data...", "the PSD image resources..."
- multichannel: "32-bit Multichannel isn't supported"
- abr: "not a readable brush file (.abr)"
- gradient import: "not a readable gradient file (.grd)"
Translations: updated the changed tl! sources in all 16 catalogs and
dropped the stale "Load Photoshop brushes (.abr)" entries.
Installer / OS metadata: de-branded the nfpm description, the PSB MIME
comment, the AppStream summary/description, the macOS UTType
descriptions, the Windows ProgId Description and the desktop launcher's
Keywords. The Photocraft.PhotoshopDocument ProgId and the
image/vnd.adobe.photoshop MIME/UTType stay: machine identifiers,
changing them hurts file fidelity and Open-with registration.
Command params docs (agent-facing): dropped Photoshop from the
type_cmds kernPair text, the layer_menu_cmds Advanced Blending defaults
and the mask_view_cmds backslash-key text. The "photoshopManages" wire
value stays. Comments, format identifiers and the "photoshop" theme
alias are out of scope and unchanged.
The macOS native-menu shortcut resolution messages no longer say "as in
Photoshop" (Hide/Minimize).
The Color Lookup properties' Load 3D LUT… entry opens the platform file picker (filtered to .cube, .3dl and .look, on desktop and web) and embeds the chosen LUT into that adjustment layer, replacing the typed-path field. A cancelled or invalid pick leaves the layer unchanged (#1749).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flat image with transparency (alpha channel or tRNS) opens as a normal, unlocked Layer 0, as in
Photoshop; opaque images still open as the locked Background. A lone Layer 0 saves back to PNG natively.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host lists fonts in fonts/manifest.txt beside index.html (craft-fonts' manifest format, paths
kept inside the site); the web shell lists those families in the font menus and fetches a family
when it is picked or a layout needs it, then re-renders the affected type layers without a history
step. No manifest changes nothing; package.sh copies one in with PHOTOCRAFT_WEB_FONTS_DIR. The
style menu also lists the weights of a variable font's wght axis.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export dialogs suggested a bare file name, so the save dialog opened wherever the OS chose.
pick_save now joins a bare suggestion onto the active document's folder (an explicit directory
or an untitled document is left as is), and the desktop dialog starts in the suggestion's folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bad control token, token plus token file, or a missing automation root printed an
error and exited 0, so a launcher believed the control server was up. Now exits 2,
like a bad --control port; subprocess tests cover the four paths. Fixes#1816.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open dialogs (File > Open and the single-file pickers for Place, scripts, notes and presets) now carry the most recent file's folder as initial_dir, and the desktop shell hands it to rfd's set_directory. Flips UI-217-3 to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS AppKit runs Edit > Paste's key equivalent and takes the
key-down, but winit still delivers the key-up (it forwards key-ups
while Cmd is held). The native menu fed egui a press plus a made-up
release, and clipboard_keys treats a Cmd+V release without a paste
event as an image paste (egui-winit drops Cmd+V when the clipboard
holds no text). So one Cmd+V pasted three times: the menu's press,
its synthetic release, and winit's real release.
- native_menu: a key equivalent adds only its press, inserted ahead of
its key-up when that is already queued; winit delivers the release.
- clipboard_keys: a Cmd+V key press that reached egui marks the paste
as seen, so its release doesn't paste again; any V/Insert release
clears the mark (releasing Ctrl before V left it stale and swallowed
the next image paste on Linux/Windows).
The portal and zenity match rfd's *.ext globs case-sensitively, so IMG_0001.JPG or DSC_0001.NEF didn't show. On Linux and the BSDs each letter of an Open filter extension becomes a [jJ] class; Windows and macOS keep plain extensions.
Duplicate #1682 by @switch87 took the same approach.
Co-authored-by: switch87 <switch87@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS and Windows clippy rejected it as dead code (-D warnings): its only caller in main.rs is
#[cfg(target_os = "linux")]. Also puts read_rendering_prefs' doc line back above it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
On a Wayland session dropping a file on the window does nothing: winit
0.30 (eframe 0.36) has no Wayland drag and drop (#386). The only way
out was launching with `WAYLAND_DISPLAY= photocraft`.
Add Preferences › Performance › Linux display server (auto | x11,
applies at next launch), direction 2 in #386. With x11, main.rs asks
winit for its X11 backend through eframe's event_loop_builder hook
(EventLoopBuilderExtX11::with_x11), so the window runs through XWayland
and drops work. No environment is modified (no unsafe). It only applies
when an X server is reachable (DISPLAY set); otherwise winit's default
is kept, so a missing XWayland can't stop the app from starting. Other
platforms ignore it.
The Wayland drop notice now points at the preference instead of the
environment variable (all catalogs updated).
Tests: the startup reader (x11, auto, unknown value, wrong type, broken
JSON, missing file), the notice text, prefs_usage, label translations.
Verified on KDE Plasma 6 Wayland: with x11 the window is an X11 window
(XWayland) and dropping a NEF from Dolphin opens it.
Since the native menu bar (#894), Quit runs file.exit, which closes the window through eframe
while AppKit's run loop is still going. AppKit's Touch Bar finder then removes a KVO observer from
the already-deallocated view in the next display cycle (`-[_NSTouchBarFinderObservation
invalidate]`): an uncaught exception that aborts on macOS 27 (#1458) and leaves a windowless app
on macOS 26 (#1575). The window's close button took the same path before #894; ⌘Q used winit's
default menu, which calls terminate:.
Every way of leaving (Quit, ⌘Q, the close button, the OS quit event, app.quit) arrives at
guard_window_close as a close request. Once leaving is settled (nothing unsaved, or the prompt
answered) it now runs Services::quit when the platform has one, keeping the window open for it.
On macOS that is -[NSApplication terminate:], scheduled on the main queue so winit is out of its
event handler and eframe's exiting hook still saves the window layout.
Reproduced in a macOS 26.6.2 VM with XCUITest: on main, Quit, Quit with a document, ⌘Q and the
close button abort with the #1458 stack; with this change all of them exit, write no crash report
and save ui.ron.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A complete Greek catalog (el.tsv) registered with the other complete-menu languages: menus,
dialogs, preferences, filters, tool names (Greek with the English name in parentheses) and
font weights, plus Greek Services / Hide Others / Show All in the macOS app menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Type options bar and Character panel swatches open the full Color Picker (Text Color),
applying to the selected characters or the whole layer via type.setStyle; Cancel is inert. A
shared "Pick screen color" action in the full dialog and compact popups samples the desktop:
X11/Windows/macOS snapshots with an 11x11 magnifying loupe (4x-24x), the XDG PickColor portal on
Wayland, the EyeDropper API on the web. Captures stay in memory, are bounded at 64 MP, and are
ICC-converted to sRGB where the display publishes a profile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
* Wayland: drop guidance offers paste and this install's XWayland command (#338, #386)
winit 0.30 (eframe 0.36) delivers no file drops on Wayland. The notice told every
user to run `WAYLAND_DISPLAY= photocraft`, which does nothing for the AppImage
(no `photocraft` on PATH) or the Flatpak (fallback-x11 grants no X socket while
Wayland is available). It now names the command for this install (Flatpak flags,
the quoted $APPIMAGE path, or `photocraft`), omits it without an X server, and
offers pasting a copied image file with Edit › Paste's shortcut, which works on
Wayland since #384. The start screen hint on Wayland says the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Wayland drop notice: Polish translations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* File dialogs no longer block the event loop (#673, #574)
Open, Save As, Export As, Place and every other file dialog ran
rfd's synchronous dialog inside winit's event handler. On macOS that
runs AppKit's modal loop there, and an event arriving meanwhile
aborted the app (SIGABRT, panic_cannot_unwind); on Linux the frozen
event loop made compositors offer to kill the app.
The platform shell now shows dialogs through one asynchronous
Services::file_dialog: the request is queued with what to do with the
answer, shown at the end of the frame with the window as its parent
(a sheet on macOS, an owned modal dialog elsewhere) and awaited on a
helper thread; the answer comes back through a channel that wakes the
app, which continues the action on a later frame. One dialog at a
time. The unsaved-changes prompt waits behind Save's dialog and closes
the document only once it saved. The web build uses the same service.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* brush picker: Import ignores the Open dialog's result, like the other callers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- photocraft-psd: clean-room .aco (v1/v2, UTF-16 names, RGB/HSB/CMYK/Lab/Gray at 16 bits,
other spaces kept verbatim) and .ase (groups, global/spot/process, RGB/CMYK/LAB/Gray floats,
unknown models kept) readers and writers; byte-stable round trips, hostile input -> Err.
- engine: presets::swatches (colours in their own model, display/foreground conversion through
photocraft-cms working spaces, persisted with the prefs, default 40 colours in 4 groups) and
swatch_cmds (swatches.list/add/delete/rename/move/newGroup/renameGroup/deleteGroup/use/reset/
import/export) with graceful-failure tests.
- ui: swatches_ui.rs replaces the const grid (groups, tooltips, click = foreground and selected
type, Alt/Ctrl-click = background, rename/delete/move, panel menu Import/Replace/Export/Reset),
Color Picker "Add to Swatches", .aco/.ase via File > Open and drag-and-drop; strings translated
in every complete catalog.
- Scorecard FILE-215-14 -> done.
Co-authored-by: leon <claude.udder842@passmail.net>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Closing or reloading the browser tab dropped unsaved documents without a
word, while the desktop app asks before closing its window. The web shell
now registers a beforeunload listener that has the browser show its own
"Leave site?" prompt while a document has unsaved changes (the new
PhotocraftApp::has_unsaved_work, refreshed every frame).
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
Co-authored-by: Claude <noreply@anthropic.com>
NSScreenNumber was coerced with `as integer`, so a CGDirectDisplayID above
AppleScript's integer range (2^29) came back as a real, "2.077748985E+9".
parse_reply rejected it, dropped the only display, and the error quoted
the whole raw line, base64 ICC profile included, in the Display profile
notice and Color Settings.
The helper now asks NSNumber for stringValue; the parser also accepts an
exact scientific-notation id; and the error names only the start of the
first field, capped at 80 characters.
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
Co-authored-by: Claude <noreply@anthropic.com>
A start that returned an error kept its gpu-starting.json marker so the
next start would try a safer backend. On the CPU path there is no safer
backend: the marker made every later start plan CPU again and fail the
same way (seen under XWayland, where the GL adapter can't present: "no
graphics adapter found"), until the file was deleted by hand.
Keep the marker after an error only when a safer backend exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wayland compositors give a pen only to clients that bind zwp_tablet_v2,
which winit 0.30 doesn't, so on Wayland the pen did nothing in the
window. With a pen attached (BTN_TOOL_PEN in /proc/bus/input/devices),
$DISPLAY set and the X11 libraries installed, the window now opens
through Xwayland, where the X11 reader gets pressure, tilt and the
eraser. PHOTOCRAFT_NATIVE_WAYLAND=1 keeps native Wayland.
macOS: the AppKit event monitor was installed before winit created the
shared NSApplication; it now goes in from eframe's app creation. The
pen's button-up (pressure 0) no longer zeroes the pressure of the
stroke's last moves, which reach the UI in the same frame.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Embed PhotoCraft CLI Windows icon and version metadata
The CLI crate now sets build to build.rs and takes winresource 0.1 as a
build dependency. On a Windows target the script embeds photocraft.ico
and sets ProductName to PhotoCraft, FileDescription to PhotoCraft
command-line interface, CompanyName to Learning Machines LLC, the shared
copyright line, OriginalFilename to photocraft-cli.exe, and InternalName
to photocraft-cli. Version strings keep the Cargo package version,
including a pre-release tag, and the numeric file and product versions
are major.minor.patch.0. A missing resource compiler prints a cargo
warning and the link continues, while PHOTOCRAFT_REQUIRE_WINRES makes
the script return an error that names rc.exe or windres. package.ps1
runs check-cli-resources.ps1 after the PE subsystem checks and before
signing, including SkipBuild artifacts, and that script checks those
VERSIONINFO fields against the package version and compares the icon
Windows extracts from the CLI with photocraft.ico drawn at 32x32,
without launching the binary. docs/releasing.md records the same CLI
resource contract beside the existing GUI one.
Refs #613
* Drop the untested check-cli-resources.ps1 release gate
It has never run on Windows, and a misfiring 200-line gate in package.ps1 would block
releases. The CLI build.rs icon/VERSIONINFO stays; PHOTOCRAFT_REQUIRE_WINRES=1 in
package.ps1 already makes a missing resource compiler a build error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* SVG opens as shape layers and places as a vector smart object (FILE-215-9, part of #215)
crates/io/src/svg.rs: usvg parses (already in the tree through egui_extras); paths become
ShapeLayer knots with the transform chain baked in, quadratics raised to cubics and every
subpath after the first joining the component so the fill rule spans the path as SVG does;
groups keep opacity and blend mode (transform-only and id-only wrapper groups vanish);
text is shaped into outline shapes with the craft fonts plus the system's; gradients become
the layer's gradient fill (stops and angle, with a warning). Clip paths, masks, filters,
pattern paints and images are rasterised by resvg into pixel layers over their bounding box
rather than dropped. Limits: 16384 px canvas, 2000 elements, 24 M px per raster, bounded
recursion; malformed input is IoError::Svg.
smart_cmds::render rasterises an SVG source at the placement's axis scale (1/16 steps,
cached per scale) and divides the placement by it, affine and projective alike; place_bytes
renders that cache up front. svg/svgz join the openable lists; book page; scorecard row done.
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
* SVG import: never read local files from <image href>
usvg's default image resolver reads any absolute or relative path in an
<image href>, so opening an untrusted SVG could embed local files
(/etc/..., ~/.ssh/...) into the document. Parse with a resolver that
only accepts data: URLs (resources_dir None, string hrefs ignored).
Embedded sub-SVGs already get no <image> resolution from usvg. The font
database (and its system-font scan) is now only loaded when the drawing
may contain text. Regression test: a PNG on disk referenced by absolute
path, file:// URL and relative path is not loaded; a data: URL still is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: leon <claude.udder842@passmail.net>
Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(apps): a non-Unicode argument is a usage error, not a startup panic (#1108)
Both binaries read `std::env::args()`, which panics on an argument that is
not valid Unicode: `photocraft-cli info $'caf\xe9.png'` died in
`library/std/src/env.rs` before printing anything, and a file manager
handing the desktop app a Latin-1 file name would do the same before any
window opened.
CLI: `photocraft_cli::run_os` takes the raw `OsString`s, and an argument
that does not convert is a usage error naming its position and a lossy
rendering (exit 2, with the usage text), like any other bad argument.
`main` calls it; `run(&[String])` is unchanged for in-process callers.
Desktop: the loop matches on `OsStr::to_str`. Flag values that are paths
(`--control-token-file`, `--automation-*-root`) become `PathBuf`s directly
and so keep working with any name; a non-Unicode `--control-token` is a
control-argument error like a bad `--control` port; non-Unicode file
arguments are collected and reported in a "Could not open" notice once the
app is up, so the launch still succeeds.
Test: `a_non_unicode_argument_is_a_usage_error_not_a_panic` (unix) runs
the binary with a `caf\xe9.png` argument for `info` and `convert` and
asserts exit 2, the message, and no panic output.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* photocraft-cli: put run()'s doc line back above run, not run_os
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Fix#1271, fix#1316: Preferences › Interface › System Title Bar
Since #986, Windows and Linux draw PhotoCraft's own one-row title bar
with its own window buttons. On tiling window managers those buttons do
nothing, and some Linux desktops look better with their own decorations.
The new System Title Bar preference (off by default) gives the window the
system's title bar and buttons back; it is read when the app starts.
macOS always uses the system's. Translated in every shipped language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
* Scorecard: count the System Title Bar preference
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: GPU startup fallbacks, a lost wgpu device,
the crash guard's panic report. A launch from a desktop menu has no
terminal either, so there was nothing to attach to a bug report.
apps/photocraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/photocraft.log; each launch
rotates the previous log to photocraft.1.log and .2.log, so the log of
a run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
(bounded) and written once the file is attached
- default levels: info for photocraft*, warn for everything else;
RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
error as the only sink
The logger is installed first in main (so the panic hook is recorded) and
the file is opened after argument parsing (--version and usage errors
leave no file). Documented in docs/development.md (Logs).
* Contributors: credit switch87 as Gert Pellin
* Desktop: report an unusable log folder through the logger
eprintln! panics when standard error is a broken pipe (a GUI launch on
Windows); log::warn! reaches standard error the same way once the file
sink has given up, and never panics. Same fix as storytold/vectorcraft#465.
* fix(cli): report missing font substitutions in run, info, and convert (#1251)
* test(cli): verify missing fonts in run/info/convert and no duplicate or false warnings
* docs(cli): document missing-font fallback warnings in convert, info and run
* style(cli): apply exact rustfmt format for multi-command warning test
File › Open, the web build, batch folders and the CLI now pick up
.afdesign, .afphoto and .afpub, which the native reader handles; the MCP
test checks the preview fallback's new warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
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.
* macOS: use the native menu bar instead of the in-window menus (#887)
On macOS the menus move from the title bar to the menu bar at the top of the
screen, built with muda from the same rows as the in-window bar (menu_items).
Linux, Windows and the web keep the in-window menus, and so does macOS with
--in-window-menus or PHOTOCRAFT_IN_WINDOW_MENUS=1.
The Mac layout (native_menu.rs, platform-free and tested everywhere):
- a PhotoCraft app menu: About (from Help), Settings (Edit › Preferences),
Services, Hide, Hide Others, Show All, and Quit, which is still file.exit so
the unsaved-changes prompt runs;
- a Window menu AppKit knows (Minimize, Zoom, Bring All to Front, the window
list) and a Help menu with the system search field;
- PhotoCraft keeps Photoshop's ⌘H (Extras), ⌘M (Curves), ⌘W (Close) and
⌘, (Hide Layers): Hide and Settings have no shortcut, Minimize is ⌃⌘M.
Key equivalents are handed back to egui as the key presses they were, so
shortcuts run through shortcuts::handle exactly as before: text fields keep
⌘C/⌘V/⌘Z, dialogs and Camera Raw stay modal. A click runs the command like an
in-window click. The menu is updated only when state changes, in place, and
rebuilt only when its structure does.
On every platform:
- menu rows show the shortcut that runs them: 12 live items (Undo, Redo, Cut,
Copy, Paste, Hide Layers, …) showed none;
- Image › Mode, View › Proof Setup, Proof Colors, Gamut Warning and Isolate
Layers stay check items with no document open, so opening one doesn't
change the menu's structure.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Native menu: test it against AppKit; fix the Window/Help menus and ⌘R routing
tests/mac_menu_appkit.rs (harness = false, main thread, like the tablet
crate's monitor test) installs the real muda menu and reads AppKit's menu
bar back: the PhotoCraft app menu, Quit ⌘Q, no Exit in File, the Window and
Help menus registered with NSApp, and Layer › New › Layer… enabled in place
(same NSMenu) once a document opens. It then sends a real ⌘R key-down
through -[NSMenu performKeyEquivalent:], which must reach egui as ⌘R and run
View › Rulers once, and chooses Rulers by click and by ↩ in the open menu,
which must run as clicks.
It found two bugs, fixed here:
- Window and Help were never registered: muda looks the submenus up in the
installed main menu, so set_as_*_menu_for_nsapp must come after
init_for_nsapp. The Window menu didn't list windows.
- AppKit matched ⌘R to Filter › Lens Correction (⇧⌘R), and the menu handed
egui the matched item's shortcut, so ⌘R ran Lens Correction. Key
equivalents now go back with the modifiers actually held.
Also a kittest: the title bar draws no menu titles with the Mac menu bar.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Mac app menu: Language and Appearance, Hide on ⌃⌘H (aligned with VectorCraft)
App settings live in the app menu, as on a Mac and as VectorCraft does:
- Language ▸ after Settings: Auto, then each UI language in its own name,
the interface.language preference checked; choosing one runs prefs.set
(unknown codes are refused).
- Appearance ▸: the themes (now check items, in-window too) and Next Theme
move from Window › Theme to the app menu.
- Hide PhotoCraft takes ⌃⌘H when ⌘H is View › Extras, as Photoshop does
(Minimize already takes ⌃⌘M).
The other Craft apps use the same app menu, Window menu and
--in-window-menus / <APP>_IN_WINDOW_MENUS opt-out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
* Windows and Linux: one title bar with the app icon, like Photoshop
The window had the OS title bar stacked above PhotoCraft's own top bar. On
Windows and Linux the window now has no OS decorations and the top bar is
the title bar, as in VectorCraft and Photoshop on Windows:
- the app icon (brand.rs) leads the bar, before the menus;
- Minimize, Maximize/Restore and Close sit flush in the top-right corner
(Close turns red on hover; it runs File > Exit, so unsaved work is asked
about first);
- the free gap between the menus and the controls still drags the window
and a double-click there maximizes it;
- invisible 5 pt edges (12 pt corners) resize the window.
The right-hand group gets its own rect between the menus and the caption
buttons (egui's MenuBar takes the whole row, so it used to be laid out from
past the bar's end). Narrow windows drop what is also in a menu before
anything overlaps: Discord, then the theme toggle, then search, then the
workspace switcher narrows. macOS keeps its traffic lights.
New theme tokens caption_close / caption_close_text, Minimize / Maximize /
Restore in all 12 catalogs, and a --custom-titlebar flag for the snapshot
example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Long right-click menus scroll inside the window instead of running off it
widgets::menu_scroll caps a context menu at the visible window height
(clear of a taskbar a too-tall window runs under, #315). egui moves the
popup up to fit, and it scrolls only when it is taller than the window.
Used by the Layers, canvas tool, Channels, Paths and document-tab menus;
the menu bar's menus already scroll (menu_nav::level).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Lossy WebP: a pure-Rust VP8 encoder with quality and lossless controls (#648)
crates/codecs gains codecs/vp8: a clean-room VP8 key-frame encoder from RFC 6386
(boolean coder, 16x16 and 4x4 intra prediction with per-macroblock mode decision,
forward DCT/WHT paired with the bit-exact inverse transforms, dc/ac quantiser tables,
token coding with per-frame probability updates, skipped macroblocks). webp.rs wraps
the frame in the RIFF container (bare VP8, or VP8X with ALPH alpha and ICCP/EXIF/XMP).
EncodeOptions: webp_lossless now picks the encoder instead of erroring, webp_quality
(0-100) sets the quantiser; a lossy WebP reports a LossyCompression fidelity warning.
Export As: the WebP entry gets Quality and a Lossless switch (new tl! string in all
catalogs). CLI --quality, MCP/RPC doc_save quality and the engine's JPEG-style
quality turn a WebP lossy; without a quality WebP stays lossless as before.
Tests: tests/webp_lossy.rs decodes every file with image-webp (PSNR by quality,
sizes fall with quality and are >= 4x below lossless on noisy photos, flat frames
under 1.2 KB, alpha exact, metadata round trip, odd/tiny sizes, 16-bit input, the
16383 px limit as an error, text edges), plus unit tests for the coder, transforms,
prediction and header.
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
* i18n(it): Lossless (Export As WebP switch) in the Italian catalog that landed on main
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
---------
Co-authored-by: leon <claude.udder842@passmail.net>
Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Drag-and-drop: place onto the canvas in Free Transform, open at a tab slot
Like Photoshop, a file dropped on the active document's canvas is placed as an
embedded smart object layer and enters Free Transform (Esc takes the place back,
Enter makes place + transform one "Place Embedded" step; several files are placed
one after another). Dropped on the tab strip, it opens with its tab at the slot
under the pointer, shown by an insertion line while dragging. Elsewhere it opens
as before.
winit 0.30 drops carry no position, so the desktop shell reads the pointer from
the OS (apps/photocraft/src/cursor.rs: AppKit, x11rb, winsafe; no unsafe).
* Drop product names from comments, translate the place error, keep move_item panic-free
- Reword the new comments that named another product ("the reference app").
- "Couldn't place {name}: {error}" is a tl! string with entries in every
complete catalog; `failed` now takes the finished message.
- The now-public `move_item` returns None for an out-of-range index instead of
panicking (regression test in move_document's test).
* Tab moves as a document.move command, views follow their documents, drop the windows-sys lock bump
- document.move ({document?, to}) reorders tabs for the UI, agents and scripts; tab-slot drops and
background opens go through it.
- sync_views keeps each view and its windows with their document by DocId, so moving or closing a
tab never hands its view to another document.
- Restore os_pipe's windows-sys 0.52 lock entry (unrelated to this change).
- cursor::client_points: Windows pixel-to-point conversion, tested across monitors with different DPIs.
- de and pt-br strings for the place error.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(photocraft): reject an unparseable --control port instead of ignoring it (#701)
The app read --control and PHOTOCRAFT_CONTROL_PORT with parse().ok(),
so a typo (78787), a missing value, or a following flag consumed as the
value left the app running with no control server and no diagnostic.
Validate both sources, name the offending value, and exit non-zero,
matching photocraft-cli serve --port. A missing --control value no
longer swallows the next argument.
* Exit with status 2 on a bad --control port instead of 0
A launcher or agent could not tell a rejected control port from a normal
quit. The collected port errors now map to exit status 2 (command-line usage
error) through control_args_exit_code, which is unit-tested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Fenty <bfenty@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
wgpu's default DX12 compiler (`Auto`) loads `dxcompiler.dll` by name, and the Windows DLL search
falls through to the current directory and PATH, so another program's DXC build (a Chrome or SDK
folder) was loaded and crashed shader compilation or failed device creation. The app now uses FXC
(d3dcompiler_47.dll, part of Windows) unless a dxcompiler.dll ships beside photocraft.exe, which is
loaded by its full path. WGPU_DX12_COMPILER still overrides.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Templates show up in the Open dialog and open as the first free
Untitled-N without a path, so File › Save asks where to save instead of
offering the template. They still go to Open Recent. This covers every
way a file opens: File › Open and Open Recent, drops and launch
arguments, background opens, the control channel's app.open, file.openAs
and headless doc_open.
Maintainer decision on #672: the app's Save As keeps TIFF layers as Photoshop does (it sets
ExportOptions.tiff_layers from its Layers option), but scripted and agent saves stay flat by
default so conversions keep predictable sizes. #672 merged with the default on, so every
headless TIFF save became layered.
- photocraft-io: ExportOptions::default() has tiff_layers: false. The desktop and web Save As
already set it explicitly from their settings, so the app is unchanged.
- photocraft-cli: --tiff-layers on convert, run and batch (listed in the usage text).
- engine: file.saveACopy, Batch, Image Processor, the export-to-files commands and the lens
batch take "tiffLayers": bool=false, through one SaveOpts { quality, tiff_layers }.
- automation: serve doc.save and MCP doc_save take tiffLayers (default false).
- Tests: default ExportOptions write a flat TIFF; the CLI writes flat without the flag and
layered with it (run and convert); #672's layered-TIFF tests ask for layers explicitly.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Flat exports embed no document XMP by default; Export As gains a Metadata choice (#647)
The document's XMP packet lists the text of every type layer
(photoshop:TextLayers) and one id per placed document
(photoshop:DocumentAncestors) - data a flat web image should not carry:
14-116 KB per export on real designer PSDs, and layer text leaking into
images that show only part of the design.
- io: ExportOptions.xmp (XmpEmbed::All | None, default None) drops the
packet from flat exports before the codecs see it. Layered saves
(PSD, PSB, .pcraft) always keep everything; DPI, ICC and EXIF are
unaffected. Export As, Save a Copy, the CLI, Layers/Artboards to
Files and Batch all share this path and get the safe default.
- The Export As dialog gains a Metadata dropdown (None / All, default
None, like the source application's Export As); the file-size
estimate follows the choice.
- Tests: new xmp_export.rs covers the default (png/jpg/webp/tif carry
no packet), the All opt-in and the layered always-keep;
metadata_roundtrip_png and the stale-orientation test document the
new contract (with All, an embedded stale orientation is still
rewritten upright).
* Export As alone starts at Metadata: None; Save As and conversions keep the XMP
ExportSettings::default() and ExportOptions::default() also serve File > Save As, file.saveAs,
the CLI and automation, which keep the document's metadata (as Photoshop's Save As does).
Only the Export As dialog defaults to None. A layered TIFF honours the choice too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Actions: actions.record/stop/play/list commands for agents and the CLI (part of #219)
The Actions list moves from UiState to the engine Session, so the panel, photocraft-cli, the control channel and MCP share one list. actions.play checks every nested step with the same authorization a top-level command gets in untrusted sessions.
* Regenerate docs/scorecard.md after rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>