Commit Graph
3466 Commits
Author SHA1 Message Date
callmeBallochandClaude Opus 5.5 508fafd92d AF.3: compare image sizes as the unsigned values they are
Clippy (-D warnings) flagged an always-false comparison and a no-op
cast in the Affinity image resampler after Rect::width became u32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-08 19:13:50 +00:00
Brandon ThomasandClaude Opus 5.5 dd555217ac Slices test: as_chunks for the fixed 3-wide rows (clippy chunks_exact_to_as_chunks) (#1374)
Follow-up to #991: Rust 1.99's clippy flags chunks_exact with a constant
size, which turns the workspace clippy gate red.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:06:06 -04:00
yangandClaude Opus 5.5 e1a874d9e9 Timeline: out-of-range duration and fps are rejected; stepping never panics (#1009) (#1023)
timeline.create and timeline.setProps kept any `duration` (up to u64::MAX) and
any `fps`, and timeline.nextFrame/previousFrame then moved the playhead with
`(current as i64 + delta).clamp(0, duration as i64 - 1)`, which panics when the
upper bound goes negative (duration 0 or above i64::MAX) or overflows.

Both commands now reject a `duration` outside 1..=1000000 and an `fps` that
isn't finite and in (0, 1000] with BadParams before anything changes. Stepping
moves to a saturating Timeline::step, safe on any stored timeline (e.g. one
loaded from a file with duration 0), and a non-finite stored fps falls back to
30 like a non-positive one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:05:39 -04:00
yangandClaude Opus 5.5 815b4ee0f9 Place Scale Marker: a document under 8 px tall returns an error instead of panicking (#932) (#1014)
The bar height was clamped to [2, h/4], and f64::clamp panics when h/4 < 2,
so any image shorter than 8 px panicked inside the command's run closure.
Reject such images with a bad-params error before anything changes, like the
existing bar-width check. Also keep the bar on the canvas when the label under
it, or the left margin, would push it off a small image.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:05:32 -04:00
yangandClaude Opus 5.5 0c2c268c49 image.crop rejects an origin or far edge that can't be moved within i32 instead of panicking (#959) (#1013)
crop_doc negated the crop origin unchecked, so x or y = i32::MIN overflowed;
an origin one past i32::MIN pushed layer pixels past i32::MAX and panicked on
a length mismatch while translating surfaces; and x + width past i32::MAX
saturated, silently cropping less than asked. crop now checks the far edge,
crop_doc checks the negation, and translate_doc checks that every surface
still fits before moving anything; all return BadParams naming the values.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:05:25 -04:00
Nigel Ihza Firdausy fa1f0548fb fix(engine): saturate layer slice outsets at coordinate limits (#998) 2026-10-08 15:05:17 -04:00
Nigel Ihza Firdausy bdff608a0c fix(engine): divide wide slice rectangles without integer overflow (#991) 2026-10-08 15:05:09 -04:00
Ian Swope 874867461d file.new at 300000x300000 no longer fills ~360 GB of tiles before returning (#705) (#900)
Surface::fill_rect materialized every tile it touched, so a new document's
background cost one 256x256 tile per tile slot: ~360 GB for the 300000x300000
size the command accepts. Fully covered tiles now share one Arc'd tile, so a
solid fill costs one tile plus the tile index; edge tiles are written as
before, and copy-on-write splits a shared tile off when it is edited.
2026-10-08 15:04:57 -04:00
059546680c Layer copies are numbered: "Layer 1 copy 2", not "Layer 1 copy copy" (#897)
Duplicating a layer (Layer › Duplicate, ⌥-drag with the Move tool, Layer
via Copy without a selection, New Smart Object via Copy, the Edit menu's
duplicate, Select and Mask's new layer) names the copy with
`Document::copy_name`: "Layer 1 copy" first, then "Layer 1 copy 2",
"Layer 1 copy 3"…, unique in the document. A name already ending in
"copy" or "copy N" is numbered from its root instead of growing another
"copy"; names that merely contain the word keep it.

Co-authored-by: Jason Short <3344856+jason4short@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 15:04:52 -04:00
cf758c5b04 New Document: a Clipboard preset with the clipboard image's size (#891)
When the clipboard holds an image (pixels copied in the app, or an image
copied in another app), File › New offers a Clipboard preset first under
the Recent blank-document presets, at that width and height and 72 ppi,
and selects it, so Create makes a document the size of the clipboard.
Without an image the dialog opens as before. Opening the dialog is an
explicit request, so the OS clipboard is read then, as for a paste.

Co-authored-by: Jason Short <3344856+jason4short@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 15:04:45 -04:00
Ian Swope dc256b8b39 A PSD layer with bounds but no channel data no longer aborts on a 360 GB allocation (#755) (#885)
record_surface sized the interleave buffer from the layer rectangle alone,
so a few-hundred-byte file declaring a 300000x300000 layer with no
channels made import allocate ~360 GB and abort. The channel decoders
already bound their output by the bytes present, so a buffer is now built
only when at least one channel decodes; otherwise the layer imports empty
with a warning.
2026-10-08 15:04:40 -04:00
Tanish Manku 5049a1d062 Never crash on hostile numbers: LUT sizes, Quick Selection points, link group ids (#869)
- Color Lookup: a stored document's `size` is untrusted. `size³ × 3` overflowed for sizes of
  2²² and up: debug builds panicked, release builds wrapped past the table-length guard and
  indexed out of bounds. One checked `photocraft_doc::adjust::lut3d_len` now backs the CPU
  compositor, the GPU planner and the PSD writer.
- Quick Selection: a stroke point far off the canvas (`select.quick {"points":[[3e9,0]]}`)
  saturated to i32::MAX and then overflowed on `+ 1`.
- Link Layers: `max + 1` over stored link group ids overflowed at u64::MAX (debug panic, release
  wrap that could merge unrelated layers). A fresh id is now the smallest unused one when the
  largest is taken; a selection's first group is read with `first()`.
- Vanishing Point clone footprint: the same `as i32 + 1` saturates (hardening; no reproducer).
2026-10-08 15:04:34 -04:00
RicSchonfelder 968b1f2c4f i18n: fix pt-br label collisions (Feather/Diffuse, Liquify/Dissolve) (#833)
Per maintainer review (#833): keep Feather as Difusão…, Diffuse becomes
Difundir…, and Liquify becomes Liquefazer… (Licuar is Spanish).
Separates the Fast menu and Stylize menu entries that were colliding in pt-BR.
2026-10-08 15:04:28 -04:00
bfentyandBrandon Fenty f9b72468b4 docs: stop linking readers to the private craftrules repo (#493) (#779)
The contributor docs offer github.com/storytold/craftrules as the place
to read the font rules and release playbook, but the repository
returns 404 to everyone outside the organization, so the cited text is
unreachable for outside contributors. Keep the sibling-checkout paths
and say plainly that the repo is not public and a maintainer can supply
the rules relevant to a change.

Co-authored-by: Brandon Fenty <bfenty@users.noreply.github.com>
2026-10-08 15:04:21 -04:00
BFlatandClaude Opus 5.5 3f740ccfb3 Layers panel: hovering a blend mode previews it live on the canvas (#970) (#1081)
Hovering a mode in the Layers panel's blend-mode list previews it on the
selected layer; moving off the list reverts, and only a click applies it
(one history step), as in Photoshop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:04:04 +07:00
Claude 4d4ab93f83 Select › Modify: bound the canvas-edge margin and translate Apply At Canvas Bounds
The margin Modify pads a canvas-edge selection with was twice the radius
for every operation, so Feather 1000 added 2002 px a side. It now pads by
each operation's reach (Feather 1.5 r, the others r) and refuses a padded
mask over 2^30 pixels instead of aborting on the allocation. The new
dialog option is translated in every shipped language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
2026-10-09 01:59:24 +07:00
Marcelo Cárdenas d7859f17c7 Select › Modify: Apply effect at canvas bounds, so Contract/Feather/Border work after Select All (#1277)
Contract, Smooth, Expand and Feather gain Photoshop's "Apply effect at canvas bounds" option
(applyAtCanvasBounds, default off). On, the canvas edge counts as a selection edge, so Select All
then Contract shrinks from the edges and Feather softens them; off, the selection continues past
the canvas, as in Photoshop. Border always follows the canvas edge, so a Select All borders to a
band inside it instead of an empty selection.

The mask is padded (empty or edge-replicated) only when the selection touches the canvas edge,
so selections clear of it cost nothing extra.
2026-10-09 01:59:24 +07:00
Claude c5d50b4390 Free Transform: a typed W/H stops at the largest document side
1000000 % on any layer could ask the transform for an image millions of
pixels wide, more than can be allocated. The W and H fields now reach as
far as the box stays within 300000 px (the document limit), at most
1000000 %, and a linked change keeps the other side within its limit too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
2026-10-09 01:58:43 +07:00
Marcelo Cárdenas bb723b12eb Free Transform: W/H fields reach 1000000% and X/Y the 300000 px document limit (#1267)
The options bar clamped W/H to ±10000% and X/Y to ±30000 px, while the engine takes any scale
and documents go up to 300000 px a side. Overlays on large stitched mosaics need more. The limits
are now named constants: ±1000000% (a 300 px layer can span the largest document) and ±300000 px.
2026-10-09 01:58:43 +07:00
callmeBalloch ef870103f8 Merge remote-tracking branch 'upstream/main' into claude/kind-darwin-btjse2 2026-10-08 18:58:33 +00:00
callmeBallochandClaude Opus 5.5 08a812e568 AF.3: open Affinity 1 and 2 extensions everywhere and match the new fallback warning
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
2026-10-08 18:58:33 +00:00
Claude 3bab0d4f52 PSD text fill: keep the fix and its tests, drop the proof-only screenshots and example
The Photopea screenshots, their ATTRIBUTION row and the example that made
them documented the bug for review; the unit tests in crates/text cover
the behaviour, so they are not needed in the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
2026-10-09 01:57:17 +07:00
Rage 7bb971f77c Fix PSD text fill after edits in Photopea 2026-10-09 01:57:17 +07:00
Nigel Ihza Firdausy 7d78e1dd2c Export Preferences: WebP quality and lossless settings for Quick Export (#1260) (#1279)
* feat(export): persist WebP lossless mode and quality in export preferences (#1260)

* fix(export): apply configured WebP quality to Quick Export output

* fix(ui): show format-specific JPEG and WebP export preference controls

* fix(ui): use WebP quality and lossless preferences in Export As dialog

* test(export): verify WebP quick export mode, quality, validation, and legacy prefs

* style: format WebP export regression test as rustfmt expects

* style: apply rustfmt layout to conditional WebP preference labels

* docs(scorecard): account for two WebP export preference fields (#1260)
2026-10-09 01:57:01 +07:00
Nigel Ihza Firdausy a5f9029c20 File menu shows the configured Quick Export format (#1259) (#1269)
* fix(ui): show configured quick export format in File menu (#1259)

* fix(ui): preserve localized quick export labels for selected format
2026-10-09 01:56:57 +07:00
Nigel Ihza Firdausy 184ec31f1c Hidden ruler guides no longer intercept canvas input (#1258) (#1268) 2026-10-09 01:56:54 +07:00
marcardenas d8dfb60b9a Gradient tool: a handle dragged past the 10% scale floor stops; the other handle stays put (#1243) (#1306)
Dragging one live-gradient handle towards the other clamps the fill's scale at 10%. For Linear the
centre was still taken from the midpoint of the requested points, so the
clamped gradient was re-centred and the handle that wasn't being dragged slid along the line.
gradient.fill.set now shifts the clamped layout so the handle not given keeps its place. The same
apply_set drives the drag preview, so the canvas matches.
2026-10-09 01:56:51 +07:00
marcardenas eb4b0f5e36 Filter › Last Filter applies to the active layer, not the layer the filter first ran on (#1270) (#1307)
The Filter Gallery, Camera Raw and Adaptive Wide Angle dialogs record the layer they ran on in
the command's params. Last Filter replayed the journal entry verbatim, so it filtered that layer
again (possibly hidden, or deleted) and appeared to do nothing on the layer you were looking at.
It now drops the recorded `layer`, so it applies to the current layer as in Photoshop.
2026-10-09 01:56:46 +07:00
Arian Pashae 93694cf2f6 chore: remove accidentally committed fb-*.log files and ignore *.log (#1155) 2026-10-09 01:56:40 +07:00
BFlatandClaude Opus 5.5 437b25bea9 A Distort corner stops before the box folds over (#1323) (#1332)
Distort and Perspective move a corner along the drag only as far as the
box stays convex, stopping just short of flat, as Photoshop and Photopea
do, instead of folding it into a bow tie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:52:27 +07:00
BFlatandClaude Opus 5.5 b3d10f9a48 The Studio themes show the Move tool's Auto-Select toggle (#1275) (#1333)
The Pro themes already had Photoshop's Auto-Select checkbox; the Studio
themes showed only a hint for the Move tool, so Auto-Select could not be
turned off there. Both now have the toggle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:52:19 +07:00
BFlatandClaude Opus 5.5 3e8d788441 Delete and Fill Selection on the selection tools' right-click menu (#1286) (#1331)
Right-click with a selection tool and choose Delete and Fill Selection
to fill the selection from its surroundings with Content-Aware Fill's
default settings, without the dialog. Engine command
edit.deleteAndFillSelection, one undo step, translated in every shipped
language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:51:47 +07:00
callmeBallochandClaude Opus 5.5 ae0313d325 AF.3: native Affinity import with artboards, vectors, text and images
photocraft-affinity now reads native Affinity documents (container
versions 8-12): the FAT and revision chain, stored/zlib/zstd entries
with predictors and CRC checks, the tagged object stream in doc.dat,
and the document model (pages, artboards, layers, groups, curves,
geometric and compound shapes, fills, gradients, strokes, text, placed
images, pixel layers, masks and embedded-document caches). Every size
is bounded and malformed input returns an error.

photocraft-io maps it to an RGB8 document with artboards, groups, shape
layers, type layers, embedded smart objects, pixel layers and
vector/layer masks; what can't be mapped is listed in the warnings. The
embedded preview is now only a fallback when the native data can't be
read, and only that preview is refused by Place and warningless imports.

`cargo xtask corpus --affinity` pins 21 public CC0/MIT documents,
sha256-verified; the io corpus test compares each import with the
thumbnail Affinity saved in it (mean difference 0-4.6 of 255).
Affinity export is not implemented: no Affinity installation was
available to validate written files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-08 18:46:22 +00:00
DWAA1660 c833294e34 PSD export: write empty layer and mask channels as raw (#1290) 2026-10-08 14:31:24 -04:00
Nigel Ihza Firdausy 5054308d0b fix(ui): allow removing the last polygonal lasso point (#1262)
* Allow removing last polygonal lasso vertex without editing layers

* Route lasso Backspace ahead of destructive edit shortcuts

* Use right-click to retract polygonal lasso points instead of opening menu

* Regress lasso vertex removal via keyboard and actual canvas right click

* Apply rustfmt layout reported by Linux CI

* Apply rustfmt layout reported by Linux CI
2026-10-08 14:31:17 -04:00
Nigel Ihza Firdausy f9ccdda1e2 fix(ui): remember previously applied New Guide Layout values (#1261)
* Remember last applied New Guide Layout parameters in UI state

* Save guide layout preferences only after successful application

* Cover successful, cancelled and failed guide layout dialog state

* Avoid overlapping borrows when reopening guide layout dialog
2026-10-08 14:31:10 -04:00
Elvis33LE f5080f9774 ui.window.open: an invalid document index is an error, not a side effect (#1133)
The document parameter's set_active result was discarded, so an
out-of-range index still switched nothing, changed nothing, and the
window opened for whatever document happened to be active - a caller
was told success for a document that does not exist. Now the call
errors with the index and leaves the active document alone.
2026-10-08 14:31:03 -04:00
317d946f45 PSD: a spec-order mask record is not misread as Photoshop's order when that reading is implausible (#949) (#1129)
real_before_parameters picked Photoshop's order (real mask first) whenever
the record length fit the parameter-flags byte at offset 36, even when that
byte was just part of the real rectangle. A density-only record in the
spec's order then read its density as the real mask's background and lost
the parameter. When both orders fit, Photoshop's reading now has to be
plausible (background 0 or 255, known parameter flag bits) or the spec's
reading with a real mask wins.

Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 14:30:56 -04:00
Ian Swope 8f5a0817e4 Applying a text data set re-renders the type layer instead of blanking it (#990) (#1025)
apply_to_doc replaced a text-replacement variable's text and set the layer's
render cache to None, but nothing rebuilt it. The compositor draws a type
layer from that cache, so the text vanished from the canvas after
image.applyDataSet and from every file written by
file.export.dataSetsAsFiles. The layer is now re-rendered with
type_cmds::refresh, as type.edit does.
2026-10-08 14:30:50 -04:00
Armaan Sandhu 3179093985 Motion Blur: apply the samples as one merged kernel over whole rows (#902)
Motion Blur took distance + 1 bilinear samples per pixel, each through Image::sample with per-tap edge checks, so distance 100 cost about 400 edge-checked reads per pixel and channel. The sample offsets are the same for every pixel, so merge their bilinear taps once into a fixed kernel (each tap an integer offset and a weight) and apply it tap by tap as a multiply-add over whole rows of a premultiplied window, which the compiler vectorizes. This is the same premultiplied mean as before, summed in a different order.
2026-10-08 14:30:43 -04:00
callmeBallochandClaude Opus 5.5 f94bb1dc9b AF.1: refresh the lockfile for the 0.5.0 workspace version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-08 17:10:10 +00:00
callmeBalloch e3932fa7a3 Merge remote-tracking branch 'upstream/main' into claude/kind-darwin-btjse2 2026-10-08 16:08:36 +00:00
callmeBalloch 1bb37eda05 AF.1: import bounded Affinity previews with fidelity warnings
Read the indexed version-12 PNG preview with bounds, chunk ordering and CRC checks. Show native-content and resolution warnings, preserve the source through normal and automation opening, and reject auxiliary routes that cannot display the warning. Add format documentation, malformed-input and save-lifecycle tests, native-oracle hooks and parser/import fuzz targets. Editable native layers and Affinity export remain unsupported.
2026-10-08 17:42:23 +02:00
Brandon ThomasandClaude Opus 5.5 e5e3e39752 Bump version to 0.5.0 (from 0.4.1) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.5.0
2026-10-08 10:01:32 -04:00
bce7e54a63 Release workflow: only signing and draft-release jobs use the release environment (#1220)
version, linux and web read no secrets. With environment: release they were refused on a branch,
so a branch dispatch of release.yml built nothing. Now a dry run builds every unsigned platform;
the signed jobs are still refused and the draft-release job needs them, so a branch can't create
a release. Matches craftrules release/playbook.md §2.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:52:09 -04:00
35f7e99965 README Downloads: where to get it first, then builds with Windows before macOS (#1219)
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:50:10 -04:00
d2a9295050 README: Downloads section listing every release build, grouped by OS (#1210)
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:24:06 -04:00
b10b37c14b macOS: use the native menu bar instead of the in-window menus (#887) (#894)
* 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>
2026-10-08 20:22:28 +07:00
Brandon ThomasandClaude Opus 5.5 c9a7d26f6a Web build: ICU complex-script dictionaries native-only; bump version to 0.4.1
The v0.4.0 release failed its web job: the wasm was 26,365,303 bytes, over the
24 MiB gate in packaging/web/package.sh and Cloudflare's 25 MiB per-file cap.
The largest single item was ICU4X's CJK segmentation dictionary (3.8 MB),
pulled in by parley's complex-scripts feature. Enable that feature on native
targets only; the web build falls back to the generic Unicode rules for
Japanese word boundaries and Thai/Lao/Khmer/Burmese line breaking.

wasm: 26,368,656 -> 22,578,266 bytes (21.5 MiB), measured with trunk build --release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:48:03 -04:00
Brandon ThomasandClaude Opus 5.5 173fc90ecb Bump version to 0.4.0 (from 0.3.0) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:12:20 -04:00