Files
photocraft/docs/scorecard.md
T
5025fd2895 Color Sampler tool (#1046) (#2753)
* Color Sampler tool (#1046)

Add the Color Sampler to the Eyedropper (I) flyout: click to pin numbered
sample points to canvas pixels, drag to move, Alt-click (or drag off-canvas)
to remove, Clear All in the options bar, at most 10 per document. Each point
reads one composite pixel (RGB 0-255 and naive device CMYK, same as the
pointer readout) and the Info panel lists them under their number with the
position in the ruler unit; the readouts refresh when the document changes.

Points are per-document engine view state (DocState.color_samplers): they
belong to their document, are discarded on close, and are not saved to
.pcraft or PSD. They are driven by commands shared by the UI, CLI, control
channel and MCP (view.colorSamplers.add/.move/.delete/.clear/.list); the
mutations change no pixels and add no History step.

Points follow the canvas geometry (crop, Canvas Size, Image Size, Image
rotation/flip, rotated crop) through a pending Session.sampler_map that
Session::edit applies before culling the points that land outside the new
canvas; the count removed is reported in the status bar.

- engine: new sampler_cmds.rs (commands + shared readout helpers) and the
  sampler_map wiring in lib.rs, image_cmds.rs, mode_cmds.rs, commands.rs,
  analysis_cmds.rs (compound rolls points back on error).
- ui-egui: Tool::ColorSampler + original sampler.svg icon, toolbar slot,
  Info panel list, options bar, pointer handling, overlay markers, cursor.
- docs/scorecard.md regenerated (TOOL-1649-1 no longer says the tool is
  missing). No menu item, so parity is unchanged (627/627).

* Color Sampler follow-up: tool count, README, i18n, roadmap

The full ui-egui suite (not just the filtered tests) surfaced these gaps:

- state.rs: Tool::ALL.len() assertion 52 -> 53.
- README.md: "52 tools" -> "53 tools" and add Color Sampler to the list,
  so readme_tool_list_matches_tool_all passes.
- i18n: the options-bar hint, "{n} of {max} sample points" and
  "No document open" need entries in every catalog that claims complete
  menus (every_tl_literal_is_translated); added to all 16 (coverage
  1584/1584). The translations were drafted alongside the tool.
- docs/roadmap.md: drop Color Sampler from the missing-tools list and note
  it landed.

* Color Sampler: fit main's new tools and Image Size resample type

Reset Tool covers ColorSampler, the test ViewXform sets aspect, and
Image Size checks Method::None instead of an Option.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 11:43:09 -04:00

67 KiB
Raw Blame History

PhotoCraft scorecard

Generated by cargo xtask scorecard from perf/budgets.toml, perf/baseline.json, crates/io/tests/corpus.rs, scorecard/*.toml and the source tree. Do not edit by hand: change the sources and regenerate (CI runs cargo xtask scorecard --check). Checklist statuses are verified against the code; numbers here supersede estimates elsewhere.

Summary

Area Done Partial Missing Key numbers
Performance 3 22 22 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 17 not run, 5 not measurable yet (baseline 2026-10-05)
File compatibility 6 12 9 io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256
Tools 25 16 2
Workspace and UI 9 8 6 settings that do nothing: 32 of 154 (target 0)
Type 1 4 4
Automation 3 2 6 631 of 631 menu items live (100.0%)
Reliability 5 2 0 never-crash lints on 28 of 28 crates; panic_hunt present
Distribution 5 4 2
Open bugs 29 2 1

Performance

Budgets: perf/budgets.toml (targets from #209, #210, #211). Numbers: the committed baseline (perf/baseline.json, full run), updated only by cargo xtask perf --update-baseline. The nightly perf-nightly workflow runs cargo xtask perf on a fixed macOS runner and fails on a broken enforced budget or a p50 regression over 15 % against a baseline from the same machine class. "Over budget" rows are targets the code doesn't meet yet: they are reported, and only their regressions fail.

Baseline: 2026-10-05, commit f5d2bcb54a, Apple M4 Pro (48 GB), GPU Apple M4 Pro, macOS 15.6; load average 224.90 / 226.31 / 196.03 (1/5/15 min) at the start of the run.

Id Scenario Target p50 ms p95 ms max ms Peak RSS GPU Status Issue
P1 Move a layer with layer styles (per drag update) ≤ 25 ms p95 321 535 535 825 MB 409 MB over budget (target not met yet) #209
P2 Change layer opacity ≤ 20 ms p95 276 443 443 639 MB 409 MB over budget (target not met yet) #209
P3 Change layer fill ≤ 20 ms p95 279 467 467 506 MB 409 MB over budget (target not met yet) #209
P4 Change blend mode ≤ 20 ms p95 206 423 423 579 MB 409 MB over budget (target not met yet) #209
P5 Toggle visibility ≤ 10 ms p95 178 425 425 668 MB 409 MB over budget (target not met yet) #209
P6 Undo a move ≤ 25 ms p95 256 530 530 650 MB 409 MB over budget (target not met yet) #209
P7 Redo a property change ≤ 25 ms p95 144 212 212 669 MB 409 MB over budget (target not met yet) #209
P8 Duplicate a 12 MP layer (first) ≤ 100 ms p95 508 699 699 907 MB 919 MB over budget (target not met yet) #209
P9 Duplicate a 12 MP layer (repeat) ≤ 20 ms p95 406 627 627 749 MB 919 MB over budget (target not met yet) #209
P10 Paste a 12 MP layer (first) ≤ 100 ms p95 1232 3136 3136 1.3 GB 1.4 GB over budget (target not met yet) #209
P11 Paste a 12 MP layer (repeat) ≤ 20 ms p95 445 1174 1174 958 MB 1.4 GB over budget (target not met yet) #209
P12 Arrow-key nudge in a 150-layer document ≤ 30 ms p95 – – – – – missing: panicked: wgpu error: Validation Error / Caused by: / In Queue::write_buffer / Buffer with 'pc_compose_uniforms' label is invalid #209
P51 New layer in a 150-layer document ≤ 16 ms p95 – – – – – not run #1771
P13 Brush dab on a 15000×10000 16-bit document ≤ 80 ms p95 6.42 146 146 839 MB 2.0 GB over budget (target not met yet) #209
P14 Move a layer on a 15000×10000 16-bit document ≤ 80 ms p95 2698 4318 4318 1.5 GB 2.0 GB over budget (target not met yet) #209
P16 Brush dab on a 32-bit document with 20 layers ≤ 100 ms p95 21.5 161 161 793 MB 1.6 GB over budget (target not met yet) #209
P17 Move a layer on a 32-bit document with 20 layers ≤ 100 ms p95 386 681 681 1.2 GB 1.6 GB over budget (target not met yet) #209
P18 Open a 20 MP layered PSD: first pixels (today: fully opened) ≤ 1000 ms p95 1753 2794 2794 2.2 GB 1.9 GB over budget (target not met yet) #210
P19 Open a 20 MP layered PSD: editable ≤ 3000 ms p95 1753 2794 2794 2.2 GB 1.9 GB pass #210
P21 Content-Aware Fill on 10 MP ≤ 1500 ms p95 218 256 256 1.8 GB 1.8 GB pass #210
P22 Spot Healing stroke on 10 MP ≤ 500 ms p95 157 247 247 1.8 GB 1.9 GB pass #210
P24 Import 500 brushes (longest UI-thread step) ≤ 100 ms max – – – – – not run #210
P25 Cancel a Gaussian Blur job on 24 MP (until the worker exits) ≤ 200 ms max – – – – – not run #210
P49 Cancel a Content-Aware Fill job on 24 MP (until the worker exits) ≤ 200 ms max – – – – – not run #210
P50 App frame time while a 24 MP Gaussian Blur job runs ≤ 16 ms p95 – – – – – not run #210
P26 Select › Modify › Smooth on 24 MP (radius 50) ≤ 100 ms p95 99.9 216 251 1.0 GB – over budget (target not met yet) #211
P27 Select › Modify › Feather on 24 MP (radius 50) ≤ 100 ms p95 122 357 360 1.1 GB – over budget (target not met yet) #211
P28 Content-Aware Scale 24 MP (to 90 % width) ≤ 3000 ms p95 954192 954192 954192 2.1 GB – over budget (target not met yet) #211
P29 Select Subject on 24 MP, repeated ≤ 100 ms p95 559 693 693 1.4 GB – over budget (target not met yet) #211
P30 Liquify 800 px brush on 3500×2200 at full resolution ≤ 1 ms per update – – – – – not run #211
P31 Camera Raw exposure change (proxy) ≤ 10 ms – – – – – not run #211
P32 Brush release on a long stroke ≤ 16 ms (no hitch at mouse-up) – – – – – not run #211
P33 Brush dab on A4 300 ppi CMYK (dab to screen) ≤ 6 ms p95 34.7 235 235 775 MB 87 MB over budget (target not met yet) #211
P36 Camera Raw exposure change with all SDR scopes (CPU) ≤ 10 ms p95 – – – – – not run #211
P40 Brush dab 60 px on 36 MP track only 1.45 3.19 3.19 1.3 GB – tracked
P41 Gaussian Blur r 40 on 36 MP track only 2963 3069 3069 1.9 GB – tracked
P42 Curves dialog change, layer preview, 36 MP track only 38.7 158 158 1.1 GB – tracked
P43 Save .psd, 36 MP, 3 layers track only 3047 3446 3446 1.2 GB – tracked
P44 Open .psd, 36 MP, 3 layers track only 3535 4547 4547 1.6 GB – tracked
P45 Full refresh with layer effects, warm caches, 36 MP (GPU) track only 105 160 160 714 MB – tracked
P46 Brush dab on a layer with effects, 36 MP (GPU) track only 30.6 83.3 83.3 863 MB – tracked
P47 Font-size drag step, 50 type layers track only 23.3 60.5 71.2 600 MB – tracked
P48 Full refresh of a 14000×14000 document track only 8715 9897 9897 – – tracked
P52 Chisel bevel size change, 12 MP ellipse, size 8 (GPU) track only – – – – – not run
P53 Chisel bevel size change, 12 MP ellipse, size 50 (GPU) track only – – – – – not run
P54 Chisel bevel size change, 12 MP ellipse, size 250 (GPU) track only – – – – – not run
P55 Chisel bevel size change, 12 MP text paragraph, size 8 (GPU) track only – – – – – not run
P56 Chisel bevel size change, 12 MP text paragraph, size 50 (GPU) track only – – – – – not run
P57 Chisel bevel size change, 12 MP text paragraph, size 250 (GPU) track only – – – – – not run
P58 Chisel bevel size change, 12 MP ellipse, size 8 (CPU) track only – – – – – not run
P59 Chisel bevel size change, 12 MP text paragraph, size 8 (CPU) track only – – – – – not run

Not measurable yet (the code can't do it yet; no number is recorded):

Id Scenario Target Why Issue
P15 Zoom / pan frame time on a 15000×10000 16-bit document ≤ 16 ms per frame no headless canvas frame harness yet (zoom and pan sample the GPU texture in the egui paint callback) #209
P20 Open a 2 GB PSD / 4–5 GB PSB opens with progress, editable, never blank opens run in the background with progress (#210), but there are no generated large-file fixtures yet (#216) #210
P23 Warp / Puppet / Perspective preview at 4K ≤ 16 ms per frame, commit in the background the previews run inside the UI tools; no headless frame harness, no background commit #210
P34 Brush preview memory, small dab on an 8K viewport ≤ 2 MiB the live brush preview buffers are UI-side; no headless counter yet #211
P35 Marquee drag on 24 MP with Alt/Shift ≤ 17 ms p95 Shift/Alt marquee modifiers don't exist yet (#208) and the drag preview is UI-side #211

File compatibility

Corpus floors (crates/io/tests/corpus.rs): the enforced minimums; the corpus tests (cargo feature corpus, cargo xtask test-corpus, always run in CI) fail below them.

Corpus Directory Files Oracle pass floor Round-trip floor
io corpus corpus/psd 170 146 169
psd-tools corpus corpus/psd-tools 309 236 308
photoshop oracles corpus/photoshop 256 133 258
Id Target Status Issue Note
FILE-215-1 Smart Objects export as embedded Smart Objects (SoLd + lnk2), not flat pixels done #215 psd_export.rs smart_blocks: PlLd + SoLd, source in lnk2 (.pcraft -> PSB); engine tests/smart_psd.rs; Photoshop 2026 opens 8/16-bit exports as live smart objects. Linked files are embedded
FILE-215-2 Smart Filters survive PSD save and re-open (filterFX descriptor read and written) partial #215 smart_map.rs: 14 filters plus a verified Camera Raw subset mapped both ways, others kept verbatim, mask in FEid; io tests/smart_corpus.rs. PhotoCraft-only filters (Twirl, Liquify…) and unmapped Camera Raw settings are left out of filterFX with a warning
FILE-215-3 Filter mask can be painted directly, with undo missing #215 smart_cmds.rs:382 mask only from the selection
FILE-215-4 Adjustments (Hue/Sat + Colorize, Curves, Levels, Shadows/Highlights) apply as Smart Filters done #215 Image › Adjustments on a smart object records image.adjustments. as a smart filter (adjust_cmds.rs adjust_as_smart_filter; the selection becomes the filter mask), re-rendered from the source by adjust_as_filter and re-editable with layer.smartFilter.setParams; PSD maps Levels/Curves/Shadows-Highlights both ways (smart_map.rs). Opening the editor from the smart-filter row is FILE-215-5's row work
FILE-215-5 Smart Filter rows: collapse, mask thumb, blend icon, drag reorder, context menu, live preview partial #215 smart_ui.rs:31 rows + visibility + editor; no collapse, drag or context menu
FILE-215-6 Smart Object corner skew and perspective (quad or homography transform) missing #215 doc lib.rs:312 SmartObject.transform is an Affine
FILE-215-7 Layered TIFF read and write (tag 37724), honouring the ask-before-saving pref partial #215 psd tiff.rs (ImageSourceData, both byte orders), io tiff_layers.rs, ui tiff_options_ui.rs (Discard Layers and Save a Copy); extra alpha channels and a Quick Mask are not written (the TIFF codec holds one transparency sample)
FILE-215-8 Large and BigTIFF import with banded decode across all IFDs done #215 codecs tiff_ifd.rs (bounds-checked IFD chain + SubIFD walk, classic and BigTIFF, cycle-safe; tiff_info / decode_tiff_page), tiff.rs banded parallel strip/tile decode, planar 1 and 2, BigTIFF write (tiff_bigtiff or > 4 GiB); io import_tiff_page; 30 MP RGB8 Deflate 345 -> 32 ms; no page picker in the UI yet
FILE-215-9 SVG opens and places as shape layers or a vector Smart Object done #215 io svg.rs: paths, groups, text outlines as shape layers (gradients approximated; clip/mask/filter/pattern/image subtrees rasterised); smart_cmds.rs renders an SVG source at its placement scale
FILE-215-10 Multi-page PDF save, PDF presentation, layer comps to PDF, vector/text output, PDF import missing #215 Only print_cmds.rs:133 print_pdf (raster print page)
FILE-215-11 RAW: lossy DNG, highlight modes, .xmp sidecars, RAW prefs wired, per-camera corpus missing #215 raw sensor.rs:210 lossy DNG unsupported; raw_defaults unread; no xmp
FILE-215-12 Camera Raw dialog shows a histogram; settings persist as a PSD Smart Filter partial #215 Interactive RGB/Lab scopes. PSD WB/Light/Presence, curves, HSL, grading, sharpening/noise detail, grain and numeric vignette controls mapped with descriptor preservation. Active manual Optics and unverified vignette style keep the updated ACR 18.4 fixture opaque; PSD/.pcraft saves preserve it. HDR, remaining settings and Photoshop acceptance still open
FILE-215-13 Video layer frames persist in .pcraft; PSD video layers mapped partial #215 format convert.rs:89 + tests/roundtrip.rs:282 persist frames; no PSD video mapping
FILE-215-14 User swatch model with ACO/ASE import and export done #215 engine presets/swatches.rs (RGB/HSB/CMYK/Lab/Gray + verbatim colour books, persisted in prefs) + swatch_cmds.rs (swatches.*); psd aco.rs (v1/v2) and ase.rs (groups, spot/global) read+write; ui swatches_ui.rs, Color Picker Add to Swatches
FILE-215-15 Layer Style contour picker, curve editor, presets, .shc import/export partial #2912 Layer Style dialog: contour thumbnail + preset picker (12 standard shapes, own control points) and Anti-aliased on Drop/Inner Shadow, Outer/Inner Glow, Satin, Bevel Gloss Contour and Contour element; Contour Editor (add/drag/delete, Corner, Input/Output, Preset, Load…/Save… .shc per Adobe's public spec, psd shc.rs); corner points render (compose contour_curve_lut) and map to PSD Cnty. Missing: a user contour library (New…, picker Load/Save/Replace Contours), Anti-aliased doesn't change the render yet, .shc not yet checked against a Photoshop-written file
FILE-215-16 Advanced Blending (knockout, blend as group, mask hides effects) end to end partial #215 doc advanced.rs + .pcraft + PSD/layered TIFF (knko infx clbl tsly lmgm vmgm), CPU compose advanced.rs (GPU falls back), blendingOptions command + dialog; 7 psd-tools knockout oracles now pass; knockout-isolated-groups (0.45 % AA edges), advanced-blending, blend-and-clipping still differ
FILE-215-17 CMYK/RGB documents preview spot inks; spot plates export (TIFF extra samples, PDF) missing #215 compose multichannel.rs:1 spot preview for Multichannel only
FILE-215-18 Save for Web per-slice settings remembered; Export As remembers and offers nearest scaling partial #215 file_ui.rs:188 last_web in-session only; export_dialog.rs:16 fixed defaults
FILE-215-19 Save As JPEG opens a JPEG Options dialog (quality 0-12, format options) missing #215 ExportSettings has jpeg_quality 1-100 only (lib.rs:118)
FILE-215-20 Artboard tool with handles and + buttons, Duplicate Artboard beside, vector PDF pages partial #215 artboard_tool.rs: Artboard tool draws, selects, moves and resizes boards by their handles; Duplicate Artboard places the copy beside (#1584); no + buttons, raster PDF pages only
FILE-215-21 Local libraries of colours, patterns, images and layers, as linked items or copies missing #215 Only preset libraries (preset_store.rs)
FILE-216-1 Every corpus fetched and enforced in CI, cached by pin done #216 ci.yml has a corpus job (name: corpus tests) on every PR and on main: the corpora are pinned in xtask/src/corpus_pins.rs, sha256-verified after both a cache restore and a download, and a missing corpus fails the tests. The cache key follows the pins, so it is cached by pin as this row asks (#190)
FILE-216-2 Large-file fixtures: 20 MP, 150-layer, generated 2 GB PSD and 4-5 GB PSB stress tests partial #216 4-5 GB PSB fixtures: psd example big_psb writes them (raw and RLE, planes past 2 GiB), io example big_psb_check opens and checks them; opt-in io tests/large_psb.rs (2.5 GB merged image). No 2 GB PSD, 150-layer or 20 MP fixture, nothing in CI
FILE-216-3 TIFF, RAW and EXR corpora read by tests; per-camera RAW coverage reported partial #216 EXR: corpus/exr (the OpenEXR deep test images, pinned) is read by codecs tests/deep_exr.rs in the CI corpus job, against the ID manifests of the upstream sidecars. corpus/tiff and corpus/raw are still copied in by hand and no test reads them; no per-camera RAW report
FILE-216-4 Exports re-open in a third-party oracle editor missing #216 The round trip is our export -> our import
FILE-216-5 Save-path tests: layered TIFF, PDF, SVG, ACO/ASE, .shc, Smart Filters, atomic-save failure missing #216 Save paths for layered TIFF, PDF, SVG, ACO/ASE, .shc and Smart Filters are absent. Atomic-save failure injection is covered: format/src/atomic/tests.rs write_failure_halfway_keeps_original and rename_failure_keeps_original (see BUG-203-6)
FILE-216-6 30-50 end-to-end workflow acceptance tasks driven through MCP or commands partial #216 automation/tests/agent_tasks.rs: 10 MCP tasks

Tools

Id Target Status Issue Note
TOOL-213-1 Pencil tool: aliased 1 px painting, square pixel-aligned cursor done #213 Tool::Pencil (B group): live aliased strokes on the pixel grid, square cursor, Auto Erase; pencil_tests.rs
TOOL-213-2 Mixer Brush tool on the toolbar done #213 Tool::MixerBrush in the Brush (B) flyout; Wet/Load/Mix/Flow/Sample All Layers options, command/control dispatch and selection-clipping tests
TOOL-213-3 Patch tool with a live preview of the patched area done #213 paint.patch (retouch_cmds/patch.rs, source/destination) + Tool::Patch (J group: lasso, then drag the selection); the drag shows the heal live (patch_preview.rs, coarse colour fit: 1000x1000 in 52 ms)
TOOL-213-4 Content-Aware Move tool reusing inpaint completion partial #213 paint.contentAwareMove (retouch_cmds/content_aware_move.rs: move/extend, Structure 1-7, Color 0-10, Sample All Layers; background job) + Tool::ContentAwareMove (J group: lasso, then drag). Missing: Transform On Drop, a live preview of the result (the drag shows the outline only)
TOOL-213-5 Painting a type/shape/smart/fill layer offers to rasterize, then continues the stroke done #213 rasterize_prompt.rs: OK rasterizes then paints (two history states), Cancel does nothing; drivable via ui.dialog.confirm
TOOL-213-6 Fill dialog: contents, blend, opacity, preserve transparency; remembered; Shift+Backspace done #213 fill_ui.rs dialog over edit.fill (fill_cmds.rs): all contents, mode, opacity, preserve; remembered in prefs; Shift+F5 / Shift+Backspace
TOOL-213-7 Live stroke preview for every brush-based retouch tool partial #213 Live (canvas.rs strokes_live): Brush, Pencil, Eraser, Blur, Sharpen, Smudge, Dodge, Burn, Sponge, Clone Stamp, Healing Brush, History Brush (#1026, #1040); the release commits the live stroke's pixels (P32). Not yet live: Spot Healing Brush, Mixer Brush, Background Eraser, Pattern Stamp
TOOL-213-8 Clone Source: W/H link, drawn overlay (opacity/blend/clip/auto-hide), Alt target cursor partial #213 Clone Source overlay drawn with opacity, inversion, circular clipping and auto-hide; W/H link, custom-tip clipping and overlay blend modes remain missing
TOOL-213-9 Eyedropper comparison ring (new colour above, current below) while sampling done #213 eyedropper_ring (canvas.rs): the sampled colour on the upper arc, the foreground below, drawn for the Eyedropper and Alt-sampling while the button is held; the Precise-cursor preference keeps the crosshair
TOOL-213-10 Ctrl+Alt drag resizes brush size/hardness with an on-canvas readout done #213 brush_resize.rs (#297, #370): Alt+right-drag, or Control+Option-drag on macOS (Control+Alt everywhere), sets diameter horizontally and hardness vertically, circle plus Diameter/Hardness readout, one coalesced tools.setBrush, no paint or history; 9 tests there
TOOL-213-11 Editable pressure curve, global and per control partial #213 Global: Preferences › Tools › Pressure Curve (prefs tools.pressureCurve; monotone cubic, engine prefs::PressureCurve; applied to every pen sample in ui-egui stylus.rs before any brush; editor pressure_curve_ui.rs with Linear/Soft/Hard presets). Missing: per-control curves (each brush dynamic's own response to pressure, tilt, rotation)
TOOL-213-12 Import an image file as a brush tip missing #213 Only brush.defineFromSelection and brush.presets.importAbr
TOOL-213-13 Brush regression tests: no seams, hard edge, zero-scatter defaults, ABR never black done #213 Seam, hard-edge and zero-scatter tests in brush_tests.rs; ABR tip colour test in abr_map/tests.rs
TOOL-213-14 Tablet: no OS arrow cursor over the canvas on macOS, Windows and Linux partial #213 Brush tools set CursorIcon::None (canvas.rs:1259-1275); not verified per OS
TOOL-213-18 Pattern Stamp tool: sibling of Clone Stamp in the S flyout done #213 Tool::PatternStamp in the S group; paint.patternStamp stamps the Patterns-panel tile with Aligned and Impressionist (an approximation: it jitters each dab's tile phase, not Photoshop's smeared impressionist look); engine tests cover depths, seams, hostile input
TOOL-214-1 Select and Mask workspace: view modes, Refine Edge brush, 6 outputs partial #214 select.refineEdge dialog with a live preview in Photoshop's view modes: Onion Skin, Marching Ants, Overlay, On Black, On White, Black & White, On Layers, per-view Opacity, Show Edge (J), Show Original (P), F cycles, X shows the image (select_mask_view.rs, #1754); no workspace layout or Refine Edge brush
TOOL-214-2 Marquee: Shift square, Alt from centre, live W x H readout done #214 canvas.rs marquee_corners: Shift square or circle, Alt from the centre, live W x H readout beside the cursor (marquee_readout); marquee_tests.rs
TOOL-214-3 Color Range test: Sampled Colors at Fuzziness 0 selects the exact match only partial #214 Fuzziness-0 tests cover midtones/shadows only (selection_cmds.rs:444-447)
TOOL-214-4 Move tool: box-select layers, Auto-Select hover outline, double-click fit, drag to a tab partial #214 Auto-Select hover outline (Preferences › Tools › Show Highlight on Rollover, #2834) and box-select layers from an empty spot (move_box.rs, layer.selectInRect, #2844) done; double-click fit and drag to a tab open
TOOL-214-5 Right-click menu during Free Transform (scale, rotate, skew, warp, flip, rotate 90/180) done #214 canvas_tool_menu.rs TRANSFORM_MENU: modes, Content-Aware Scale / Puppet Warp greyed, Rotate 180 / 90 CW / 90 CCW and Flip turn the open box (transform_tool::intercept, #2832)
TOOL-214-6 Puppet Warp mesh follows the object outline (constrained triangulation) missing #214 algo/puppet.rs:136 builds a block-grid mesh
TOOL-214-7 Content-Aware Scale through the Free Transform box with a live proxy preview partial #214 edit.contentAwareScale (edit_menu_cmds.rs:899) behind a parameter dialog
TOOL-214-8 A measured ruler angle prefills Image Rotation > Arbitrary (0.01 deg) done #214 analysis_ui.rs: image.rotation.arbitrary opens seeded with the ruler angle and the direction that straightens it; arbitrary_rotation_starts_at_the_ruler_angle checks 5.7106 deg within 1e-3 and cw/ccw for three measured lines
TOOL-214-9 Crop: W x H x Resolution with units, saved crop presets, Straighten in the options bar done #214 Ratio dropdown and fields, Straighten (TOOL-214-21); W x H x Resolution with units, built-in size presets and Front Image (crop_size.rs, image.crop targetWidth/targetHeight/resolution, #2443); New / Delete Crop Preset save and delete crop tool presets (crop_presets.rs, tool.presets.* with tool crop, persisted with the preferences)
TOOL-214-10 Pen: Alt breaks a handle, Shift 45 deg, Ctrl direct-selects, Backspace, close-drag handle partial #214 Ctrl/Cmd = Direct Selection and Alt = Convert Point on existing paths (direct_select.rs, #790); no Shift 45 deg, Backspace or Alt while drawing
TOOL-214-11 Stroke Path dialog: pick any brush-based tool, Simulate Pressure partial #214 path.stroke brush, pencil, eraser (vector_cmds.rs:1170); brush-only button, no dialog
TOOL-214-12 Shapes: on-canvas corner radius handles, shape feather, selection to shape with tolerance partial #214 select.convertToShape with tolerance (vector_cmds.rs:1256); radius as a field only
TOOL-214-13 Mask Properties: Density and Feather controls partial #214 Vector-mask density/feather in the engine (vector_cmds.rs:948); no UI controls
TOOL-214-14 Vanishing Point window: draw and edit planes, paste, clone in perspective partial #214 filter.vanishingPoint (vp_cmds.rs:15) behind a parameter dialog
TOOL-214-15 Content-Aware Fill workspace: paint the sampling area, preview, rotate/scale/mirror, new layer partial #214 Engine sampling auto, rect, custom (edit_menu_cmds.rs:324); generic dialog
TOOL-214-16 Liquify: redo (Cmd+Shift+Z) done #214 liquify_ui.rs supports bounded redo through Cmd+Shift+Z and the control channel; new strokes clear redo
TOOL-213-17 Red Eye tool: click a pupil to neutralize the red reflection done #213 paint.redEye (redeye_cmds.rs) + Tool::RedEye in the J flyout; Pupil Size and Darken Amount; catchlights kept; a miss is an error
TOOL-214-17 Rotate View tool: drag to turn the view, Reset View, Match Rotation copies the angle done #214 Tool::RotateView in the Hand flyout; view.rotateView / view.resetView; Match Rotation copies views[].rotation; enhanced_controls.rotate_view_with_trackpad is read
TOOL-1649-1 Eyedropper Sample Size (Point, 3×3 … 101×101 Average), Sample layers, Show Sampling Ring done #1649 document.sampleColor (sample_cmds.rs): average of the square clipped to the canvas, in the document's model and depth; eyedropper_ui.rs options bar; shared with Alt-click, dialog eyedroppers and the Info panel; ui.set eyedropperSampleSize/eyedropperSample/eyedropperRing. Color Sampler tool is separate (#1046, sampler_cmds.rs)
TOOL-214-20 Crop overlay options: Rule of Thirds, Grid, Diagonal, Triangle, Golden Ratio, Golden Spiral; Auto / Always / Never Show Overlay; O cycles the overlay, Shift+O its orientation done #1919 crop_overlay.rs: unit-square guides shared by upright and turned frames (canvas.rs), options-bar menu, O / Shift+O ahead of the tool keys in shortcuts.rs; ToolOptions crop_overlay / crop_overlay_show / crop_overlay_orientation, ui.set cropOverlay / cropOverlayShow / cropOverlayOrientation
TOOL-214-18 Crop: drag outside the frame to rotate it (Shift 15 deg steps, angle readout); Enter rotates and crops in one step done #1792 image.crop angle param (image_cmds.rs: Image Rotation > Arbitrary + crop, one undo step); crop_ui.rs turns, moves and resizes the frame along its axes (Classic Mode; default mode turns the image instead, TOOL-214-23); UiState.crop_angle
TOOL-214-19 Crop: arrow keys nudge the crop box (Shift 10 px), X swaps portrait/landscape, W x H readout while drawing or resizing done #1919 crop_ui.rs keys/nudge/swap_orientation (ahead of the selection nudge and tools.swapColors in shortcuts.rs; X also swaps the options-bar ratio) and sizing (canvas.rs draws it with the marquee readout); turned frames included
TOOL-214-21 Crop: Straighten button in the options bar and Cmd/Ctrl-drag draw a line; the frame turns so the line ends up level (or plumb when nearer vertical), with an angle readout done #1919 crop_straighten.rs: one-shot options-bar button, Cmd/Ctrl held is a temporary Straighten (ui.pointer command flag); the Ruler's straighten_angle convention; the frame is fitted to the largest of its proportions inside the image (Classic Mode); a pending edit committed by Enter
TOOL-214-22 Crop gear menu: Show Cropped Area (H), Enable Crop Shield with Match Canvas / custom colour, opacity and Auto Adjust Opacity done #1919 crop_shield.rs: fill() resolves the shield (canvas.rs paints it around upright and turned frames), gear popup in the options bar, H ahead of the Hand key in shortcuts.rs, Custom… opens the Color Picker; ToolOptions crop_shield, ui.set cropShield. Use Classic Mode / Auto Center Preview: TOOL-214-23
TOOL-214-23 Crop: Photoshop's default mode (the image turns and moves behind an upright box, Auto Center Preview keeps a resized box's centre in place and centres a drawn one) and Use Classic Mode (P) in the gear menu done #1919 crop_mode.rs: the crop model (crop_rect + crop_angle) and image.crop are shared by both modes; default mode sets the view's rotation to -crop_angle while the tool shows a frame (picking the tool, in either mode, turns a Rotate View turn upright about the view centre, not restored after Enter/Esc or by P; Classic Mode keeps the camera upright with the frame drawn turned; as Photoshop 25.1), pivoting so the box stays put, moves view and frame together for drags inside and arrow keys, keeps a resized box's centre in place live (Auto Center Preview; a drawn box is centred on release, a moved one never), readout = the image's turn; gear menu order and Classic Mode greying as Photoshop 25.1; CropShield classic_mode / auto_center_preview (ui.set cropShield), P ahead of the Pen key
TOOL-1053-1 Shape stroke controls: solid/dashed/dotted, custom dash/gap/offset, caps, joins, alignment, miter limit and opacity done #1053 shape_stroke_ui.rs: shared staged editor, renderer samples, cached Properties canvas preview; existing shape.create/edit/info; serde tool defaults, ui.set shapeStroke; pcraft and vstk style round trips. Gradient fill UI and saved stroke libraries remain open; Line retains filled-bar geometry
TOOL-213-19 Remove tool: paint over an object (or circle it) and it is filled from its surroundings partial #213 paint.remove (retouch_cmds/remove.rs, background job) + Tool::Remove in the J flyout; loops close over what they enclose; fill by non-local patch completion with texture features, onion-peel start, best-patch copy and gradient-domain seam hiding (algo/nonlocal.rs, Newson et al. IPOL 2017); sparse holes in tiles (algo/remove.rs). remove_quality on 20 holes in public-domain photos: the fill keeps 0.89 of the texture it replaces (0.54 with Content-Aware Fill's completion), edge gradient 1.03 of the original's; remove_bench 24 MP: 100 px ring 120 ms, 1000 px ring 2.5 s, 3000 px line 1.7 s. Missing: generative AI mode, Remove after each stroke off, live preview of the result
TOOL-1045-1 Color Replacement tool on the toolbar done #1045 Tool::ColorReplacement in the Brush (B) flyout after the Mixer Brush; Mode/Sampling/Limits/Tolerance/Anti-alias options drive paint.colorReplacement with the foreground colour; live preview (LiveDab with the stroke's finishing dab), one History step; color_replacement_tests.rs

Workspace and UI

Settings that do nothing: 32 of 154 (target 0, #204). Counted by the prefs audit (xtask/src/scorecard.rs): a Preferences field counts as read when code outside the prefs model (prefs.rs except its impl Session blocks; tests excluded; the schema-driven dialog never names a field) accesses section.field, or .section and .field in the same file, or calls a section method that reads self.field. It is a heuristic lower bound: a generic name can look read when it isn't.

Unread: general.color_picker, general.animated_zoom, general.zoom_resizes_windows, workspace.auto_collapse_icon_panels, workspace.auto_show_hidden_panels, workspace.enable_floating_document_window_docking, workspace.enable_narrow_options_bar, file_handling.image_previews, file_handling.save_in_background, file_handling.ignore_exif_profile_tag, file_handling.maximize_psd_compatibility, performance.cache_levels, performance.legacy_compositing, scratch_disks.disks, units_and_rulers.column_width, units_and_rulers.gutter, plug_ins.show_extension_panels, plug_ins.allow_scripts_to_connect, plug_ins.generator_enabled, type_.missing_glyph_protection, type_.text_engine, type_.recent_fonts, enhanced_controls.scrubby_slider_acceleration, enhanced_controls.touch_gestures, raw_defaults.color_space, raw_defaults.bit_depth, raw_defaults.resolution, raw_defaults.sharpen_for, raw_defaults.open_as_smart_object, raw_defaults.apply_auto_tone, integrations.allow_agent_control, integrations.control_port.

Id Target Status Issue Note
UI-217-1 Tool options, last Export As / Save for Web / Fill settings and font recents persist partial #217 Fill (fill_ui.rs:72) and Liquify (liquify_ui.rs:64, #418) keep their settings in Preferences (prefs.dialogs); no tool_options/export fields in Preferences; export_dialog.rs:21 png/85. Each painting tool keeps its own brush (paint_mouse.rs sync_tool_brush, #218), but those live in UiState, so they are lost at quit like the other tool options
UI-217-2 Window size and dock width remembered across restarts done #217 eframe persistence (ui.ron): persist_window for the window, egui memory for the dock panel width; window_and_panel_geometry_survive_a_restart asserts the round trip (apps/photocraft/src/main.rs; b07a2e5b, #587)
UI-217-3 File > Open starts in the last-used folder done #217 file_dialog.rs last_used_dir: open dialogs ask with initial_dir (the most recent file's folder) and apps services.rs passes it to rfd set_directory; open_starts_in_the_last_used_folder + last_used_dir_takes_the_most_recent_parent
UI-217-4 Preferences all take effect; recent files persist and honour recent_file_count (#204) missing #217 See the prefs audit and BUG-204-*
UI-217-5 Panel groups can float (menu + drag out of the dock) and are restored at launch missing #217 No floating groups in dock.rs/panels.rs
UI-217-6 Document tabs: overflow list, context menu (Close/Others/All/Reveal), drag to reorder done #217 canvas.rs: Close/Others/All menu with unsaved prompt; Reveal reuses file.revealInFinder (engine layer_menu_cmds::reveal); the » chevron (tab_strip) lists overflow; dragging a tab reorders via document.move
UI-217-7 Command palette shows recently run commands on an empty query done #217 palette.rs: note_recent from menus::invoke keeps the last 8 commands (dedup, newest first); an empty query lists them via recent_hits; the_palette_records_and_replays_recent_commands_on_an_empty_query covers record -> empty query -> replay
UI-217-8 Tool-specific canvas right-click menu; right-click spelling suggestions in text fields partial #217 paint_mouse.rs brush picker and canvas_tool_menu.rs selection actions; text_field_menu.rs gives every text field its right-click Undo/Cut/Copy/Paste/Delete/Select All menu (#2921); other tools missing; spelling suggestions (edit.checkSpelling suggest) not in the text-field menu yet
UI-217-9 UI scale presets 100/115/125/150/200 % through zoom_factor, remembered done #217 prefs::UiScale offers Auto plus 75 to 300 percent (13 presets, more than the five this row lists); prefs_ui.rs display_scale resolves it (Auto follows the system scale, fractional and unscaled 4K included, #1072/#1278) and sync_display_scale applies it with ctx.set_zoom_factor. It is a preference, so it is remembered. The zoom_factor assert in input_tests.rs is about canvas zoom not scaling the interface, not about this setting
UI-217-10 Appearance: animation toggle, canvas pattern toggle per theme, start-screen recents missing #217 No such prefs; start screen (canvas.rs:825) has no recents
UI-217-11 Soft dark and soft light themes with picker previews; outlined knobs in light themes missing #217 theme.rs ThemeKind has twelve themes (the four Pro brightness levels, Studio and Studio Light, Classic, Adwaita and Adwaita Dark, Solarized Dark, Breeze Light and Breeze Dark); no Soft dark or Soft light, and no outlined knobs in the light themes
UI-217-12 Save custom New Document presets; a real Recent category partial #217 document_preset_cmds.rs and new_doc_ui.rs: named presets save/select/delete through preferences; Recent remains a static list plus Clipboard
UI-217-13 Numeric fields accept arithmetic and units; scrubby labels; double-click resets a slider partial #217 widgets.rs:132 DragValue scrubs and takes + - * / arithmetic; no units in expressions, label scrub or reset
UI-217-14 Layers panel: wheel steps blend modes, drop on New/Trash duplicates/deletes, checker behind art partial #217 panels.rs: the wheel steps the blend-mode dropdown (widgets::dropdown_wheel_hovered, #1747); trash is click-only, no drop on New/Trash, no checker behind art
UI-217-15 Curves: smoothing / pencil mode, black/grey/white eyedroppers, tooltips partial #217 adjust_dialog.rs adds black/gray/white eyedroppers; smoothing and pencil modes remain missing
UI-217-16 Hue/Saturation: Saturation and Lightness sliders get hue-following colour tracks done #217 saturation_track/lightness_track (adjust_editors.rs): grey -> the hue and black -> the hue -> white, recolouring as the Hue slider moves; saturation_and_lightness_tracks_follow_the_hue
UI-217-17 Color panel: wheel plus RGB/HSB/CMYK/Lab/Gray/Web slider modes; resizes with its group missing #217 panels.rs:1837 single field + hex/RGB readout
UI-217-18 Alt+wheel zooms in gentle ~5 % steps; zoom controls uncramped missing #217 canvas.rs:1107-1125 no Alt handling
UI-217-19 Shortcut hints rendered per platform; Reveal label per OS done #217 hints and tooltips go through shortcuts::pretty (6266b7a9, #262; pretty_uses_the_platform_notation); menus::reveal_label names Reveal in Finder per OS (Show in Explorer / Show in Folder) in the Smart Objects menu and the tab menu; reveal_label_names_the_platform_file_manager + smart_objects_reveal_item_names_the_platform_file_manager
UI-217-20 'Show rulers in new documents' preference; guides stored as f64 done #217 View › Rulers is remembered in unitsAndRulers.showRulers and applied at launch and on change (#2918; rulers_open_as_last_set_after_a_restart); doc Guides are Vec through New Guide, .pcraft and PSD 1032 (guides_keep_sub_pixel_positions_through_pcraft_and_psd)
UI-217-21 One Preset Manager for every preset kind with folders, search, reorder, load/save sets partial #217 edit_menu_cmds.rs:714 covers brushes, custom shapes, patterns with reorder
UI-217-22 Stamp Visible into a new layer keeping originals; Alt+Merge keeps originals done #217 stamp_cmds.rs layer.stampVisible (Cmd+Alt+Shift+E) merges every visible layer into a new layer above the active one and keeps the originals; layer.stampDown (Cmd+Alt+E) too. As in Photoshop they have no menu item of their own: Alt + Layer > Merge Visible / Merge Down / Merge Layers run them (stamp_cmds::alt_variant, menus.rs); stamp_tests.rs alt_click_on_merge_items_stamps and cmd_alt_shift_e_stamps_visible_and_can_be_rebound
UI-217-23 Layer clipboard: copy/paste whole layers (masks, styles, type) between documents partial #217 layer.copyToDocument copies layers into another open document keeping names, content, masks, styles and blending, converting to the destination's profile and depth, as one history step (layer_copy_cmds.rs, #589). It is the command a drag between documents dispatches; there is still no copy/paste layer clipboard

Type

Id Target Status Issue Note
TYPE-218-1 Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip missing #218 doc text.rs:209 TextShape has Point and Box only
TYPE-218-2 Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret missing #218 Engine has TextDirection (text.rs:155); no UI
TYPE-218-3 Thai/Khmer/Indic fallback fonts; golden shaping tests for complex scripts and RTL partial #218 Thai is covered: THAI_FAMILIES (fonts.rs) lists the Windows, macOS and Linux families and thai_in_latin_font_falls_back_to_installed_thai_font checks a sample with above and below marks (#1909); Arabic and Hebrew fall back too. Khmer and Indic have no fallback list, and there are no per-script golden shaping tests (only a bidi test)
TYPE-218-4 Variable font axis sliders from the font's axes; opsz follows the point size partial #218 type_cmds.rs:184 accepts variations; FaceInfo.axes unused in the UI
TYPE-218-5 OpenType UI: Character panel buttons, Numerals and Stylistic Sets submenus partial #218 Type > OpenType toggles (type_extra_cmds.rs:512); no panel buttons or submenus
TYPE-218-6 Manual pair kerning (Alt+arrows), optical kerning rendered, both round-trip PSD (#206) done #218 Alt+Left/Right at a collapsed caret kerns the pair (type_tool.rs kern_pair, 20 per step and 100 with Cmd; type_tool_alt_arrows_kern_the_pair). Optical kerning is computed from the glyph outlines in text/src/optical.rs and applied by layout.rs when the run's mode is Optical (optical_matches_photoshop, optical_kerning_tightens_open_pairs). Both modes and manual values round-trip through PSD (io/tests/psd_kerning.rs; the optical mode travels in Txt2, txt2_carries_optical_kerning)
TYPE-218-7 Warp Text dialog previews live and opens with the layer's current warp missing #218 view_cmds.rs:834 always seeds arc, bend 50
TYPE-218-8 Text editing: select all, double/triple/quadruple-click selection, paste, with tests partial #218 Cmd+A, paste and double-click exist; no triple/quadruple click, no tests
TYPE-218-9 Offer 'Rasterize and continue?' for Distort/Perspective/Warp on live type missing #218 No such prompt

Automation

Menu wiring (docs/parity-checklist.md): 631 of 631 menu items live (100.0%). This counts dispatch, not behaviour.

Id Target Status Issue Note
AUTO-1 Every menu item dispatches a command id (UI, CLI, control channel and MCP share them) done docs/parity-checklist.md: all menu items live; menus.rs is_live
AUTO-219-1 Record actions in a document-independent form (implicit layer, merged type keystrokes) missing #219 actions.rs:47 copies raw journal entries with explicit layer ids
AUTO-219-2 Actions panel: step and dialog toggles, reorder, delete a step, Stop, sets, .atn import missing #219 Panel deletes whole actions only (actions.rs:146)
AUTO-219-3 actions.record/stop/play/list commands for agents and the CLI done #219 actions.list/get/record/stop/play/delete in actions_cmds.rs; the panel, CLI and MCP share them. Nested steps pass the session authorize hook.
AUTO-219-4 Replay tests for crop, filters, selections and styled text on a second document missing #219 One replay test (actions.rs:171: new layer, rect select, fill)
AUTO-219-5 Batch: subfolders, open documents, naming template, progress/cancel, error log, stop partial #219 file_cmds.rs:590-631 folder/array input, per-file errors; no recursion, template or cancel
AUTO-219-6 Plug-in ABI v2: panel kind, progress/cancel calls, read other layers, Last Filter repeats missing #219 runtime.rs:15 ABI_VERSION = 1; manifest.rs:18 Kind::Filter only
AUTO-219-7 Local ONNX models behind a flag, managed in Preferences; subject select, background removal missing #219 No ONNX runtime dependency
AUTO-219-8 xtask check that every panel button routes through a command id missing #219 No such xtask check
AUTO-219-9 Headless commands for in-canvas type editing, Liquify/Puppet strokes, transform handles done #219 type.hitTest, type.caret, type.navigate; filter.liquify strokes, edit.puppetWarp pins, type.edit, edit.transform
AUTO-219-10 Agent acceptance suite grown to 30-50 workflows partial #219 automation/tests/agent_tasks.rs runs 10 workflows over MCP

Reliability

Never-crash attribute on 28 of 28 crate roots.

Id Target Status Issue Note
REL-1 panic_hunt fuzzes every command with adversarial params done engine/tests/panic_hunt.rs: every command, own thread, 4 s timeout; #[ignore], run in the gate
REL-2 Every workspace crate carries the never-crash deny(clippy::unwrap_used, ...) attribute done Counted automatically above (crate roots under crates/ and apps/); testkit gained it in #221
REL-3 The app catches escaped panics around command dispatch and import/export done a_panic_in_a_command_... runs the test-only test.panic spec through Session::execute and asserts Err plus an unchanged document (engine jobs_tests.rs); the worker guard is covered by a_panic_in_the_worker_...; crash_guard.rs guard_turns_a_panic_into_an_error covers import/export (apps/photocraft)
REL-4 Atomic save (temp, fsync, rename) for every document write done #203 photocraft_format::atomic_write (format/src/atomic.rs, #230): same-dir temp, sync_all, rename with Windows PermissionDenied retry, Unix dir fsync; every document write site routes through it and format/src/atomic/tests.rs failure-injects write, rename and read-only-dir errors
REL-212-1 Fallible allocation for paste/erase/fill/transform/save/import, clear out-of-memory error partial #212 Raster region/tile buffers, paste/erase/fill/transform and warp output use fallible reservations; save/import boundaries preserve existing state, but encoder/decoder internals still need a broader fallible-allocation audit
REL-212-2 Export switches to PSB when the estimated file size exceeds 2 GB done #212 PSD export uses checked size estimates for raster, masks, channels, and retained metadata
REL-212-3 Tests: an allocation-limit injection makes each command Err with the document unchanged partial #212 Engine failpoint tests cover paste, erase, fill, transform, import and save transaction boundaries; codec-level allocator failure coverage remains open

Distribution

Id Target Status Issue Note
DIST-1 macOS universal build (aarch64 + x86_64), signed and notarized done release.yml:66,75,93 (macos-15); 0.2.0 shipped notarized
DIST-2 Windows x64 and x86 builds partial release.yml:113-114; signing skipped with a warning until the signing material exists
DIST-3 Linux x86_64 and aarch64: AppImage, deb, rpm, tar.gz done release.yml:162-163,186
DIST-4 Flatpak single-file bundles, x86_64 and aarch64 done release.yml:204-216 (#163)
DIST-5 Web build (wasm32, trunk) done release.yml:312
DIST-6 FreeBSD 14 x86_64 tar.gz (/usr/local tree) built in a FreeBSD VM partial release.yml:242-283 (freebsd job) + packaging/freebsd/package.sh; layout checked with --dry-run, not yet produced by a release run
DIST-220-1 Help > Check for Updates plus an opt-out periodic release-feed check on all desktops missing #220 No update check in crates
DIST-220-2 Windows ARM64 (aarch64-pc-windows-msvc) in the release matrix, signed like x64 done #220 release.yml has { arch: arm64, target: aarch64-pc-windows-msvc } in the Windows matrix, and .github/workflows/windows-arm64.yml packages that MSI, installs it on a Windows 11 ARM64 runner, checks the installed binaries are ARM64 and runs the CLI natively before uninstalling. Signing takes the same path as x64: the secrets are optional and a missing one produces an unsigned artifact and a warning (see DIST-2)
DIST-220-3 In-app bug report with reviewable diagnostics that opens a prefilled issue missing #220 No bug report dialog in crates
DIST-220-4 Translation coverage per language reported by xtask; RTL layout for Arabic and Hebrew partial #220 cargo xtask i18n-coverage reports per-language coverage (#427); RTL languages mirror the shell (direction.rs), not yet panel interiors or dialogs
DIST-220-5 Linux AppImage x86_64 + aarch64 ship; Flatpak kept; Flathub later (#173) partial #220 AppImage and Flatpak ship (release.yml:162-216); Flathub not done

Open bugs

Id Target Status Issue Note
BUG-203-1 Every document write goes through one atomic_write (temp, fsync, rename) done #203 photocraft_format::atomic_write (format/src/atomic.rs, #230/8227071); every write site routes through it: engine file_cmds.rs write_file and preset_store.rs, automation files.rs/headless.rs/workspace.rs, app services.rs write_atomic, format store.rs and autosave.rs
BUG-203-2 Temp file in the same dir, sync_all, rename over target, fsync dir on Unix done #203 atomic.rs: temp_name in the same dir, create_new + write_all + sync_all, Unix directory fsync after the rename
BUG-203-3 Windows: retry the rename with backoff on PermissionDenied done #203 atomic.rs RenameRetry::platform(): seven retries from 10 ms on Windows; retry_rename is shared with automation's cap-std workspace write
BUG-203-4 Unwritable directory errors clearly, never falls back to writing in place done #203 atomic.rs maps read-only dir, disk-full and cross-device rename errors to messages that say the original was not changed; there is no in-place fallback path
BUG-203-5 On any failure the original file is untouched and the temp file removed done #203 atomic.rs removes the temp file on every failure; tests assert the original bytes survive write and rename failures
BUG-203-6 Failure-injection tests: mid-write, rename and read-only dir errors keep original bytes done #203 format/src/atomic/tests.rs: Faulty AtomicFs injects mid-write StorageFull, rename failure and a read-only dir; locked-target retries, symlink and file-mode preservation covered too
BUG-204-1 Interface prefs (UI scale, font size, channels in colour, sliders, tooltips) wired or hidden done #204 prefs_ui.rs tick reads ui_scale (sync_display_scale), ui_font_size (theme::set_ui_font_size) and show_tooltips (sync_tooltips); show_channels_in_color is wired in channel_thumbs and the canvas channel view (#2385); dynamic_color_sliders is in prefs::HIDDEN_UNTIL_IMPLEMENTED
BUG-204-2 workspace.large_tabs, animated_zoom, auto_show_home_screen wired or hidden done #204 workspace.large_tabs controls document tab height in canvas.rs; auto_show_home_screen drives the Home screen; animated_zoom is in prefs::HIDDEN_UNTIL_IMPLEMENTED
BUG-204-3 RAW prefs and ask_before_saving_layered_tiff read, or hidden done #204 ask_before_saving_layered_tiff drives the TIFF Options prompt (ui-egui tiff_options_ui.rs); raw_defaults.open_in_camera_raw drives the Camera Raw dialog (camera_raw_ui.rs); the other raw_defaults fields are in prefs::HIDDEN_UNTIL_IMPLEMENTED
BUG-204-4 Recent files persisted through prefs across restarts done #204 crates/ui-egui/src/lib.rs::push_recent stores the list in preferences; prefs_ui::load restores it and recent_files_survive_a_restart_and_honour_the_count verifies reload
BUG-204-5 Recent-files list honours recent_file_count (default 20) done #204 lib.rs push_recent caps the list with recent_cap (recent_file_count); menus.rs covers default and custom limits
BUG-204-6 Test: every Prefs field is read somewhere or on a hidden allowlist done #204 crates/engine/tests/prefs_usage.rs every_preference_is_read_or_hidden: each field is read outside prefs.rs or listed in prefs::HIDDEN_UNTIL_IMPLEMENTED, never both
BUG-205-1 Free Transform with a selection moves only the selected part of a linked mask done #205 linked_mask_with_a_selection_moves_only_the_selected_region (transform_cmds.rs; 5a5facc, #242)
BUG-205-2 Live transform preview draws the layer with its mask applied done #205 transform_tool.rs preview_mask + preview_applies_the_linked_mask (5a5facc, #242)
BUG-205-3 edit.transform honours target mask: transforms only a targeted, unlinked mask done #205 targeted_unlinked_mask_transforms_alone and targeted_linked_mask_moves_with_its_layer (transform_cmds.rs) plus the through-the-box tests (transform_tool.rs; 5a5facc, #242)
BUG-205-4 Test: a linked raster mask with no selection moves with the pixels done #205 linked_raster_mask_moves_with_the_pixels (transform_cmds.rs): a reveal-all mask with a hidden half, linked, no selection; asserts the hidden half travels with the pixels and nothing hidden is left behind
BUG-205-5 Tests: linked mask + selection stays aligned; unlinked stays; targeted mask only done #205 All three in transform_cmds.rs: linked_mask_with_a_selection_moves_only_the_selected_region checks the mask value under each moved pixel, unlinked_mask_stays_put covers it with and without a selection, targeted_unlinked_mask_transforms_alone moves the mask while the layer bounds stay
BUG-206-1 CharStyle models per-character manual kerning separately from the auto-kern mode done #206 doc/src/text.rs CharStyle carries both: kerning: Kerning for the automatic mode (Metrics/Optical/Off) and kern: f32 for manual kerning in 1/1000 em between this character and the next, added on top of the pair's automatic kerning
BUG-206-2 PSD import keeps Optical and manual Kerning values done #206 io/tests/psd_kerning.rs manual_and_off_kerning_round_trip_through_psd and optical_kerning_round_trips_through_psd import an exported layer and compare every character's (mode, kern) pair; the optical mode travels in the document Txt2 block (#1348)
BUG-206-3 PSD export writes the real AutoKerning mode and Kerning values done #206 manual_and_off_kerning_round_trip_through_psd (io/tests/psd_kerning.rs) reads the AutoKerning flag and Kerning value of every character back out of the written TySh: (false, 100) after A, (true, -50) after T, and automatic kerning off where the style says so
BUG-206-4 Alt+Left/Right at a collapsed caret adjusts pair kerning; Character panel field done #206 type_tool.rs: Alt+Left/Right at a collapsed caret calls kern_pair (20 per step, 100 with Cmd); the Character panel field takes Metrics, Optical or a manual value (kerning_label / parse_kerning). Covered by type_tool_alt_arrows_kern_the_pair and kerning_field_values (input_tests.rs)
BUG-206-5 Tests: PSD round trip of manual and Optical kerning, a real file in the oracle corpus partial #206 Round trips covered by io/tests/psd_kerning.rs (manual, Off and Optical, plus hostile_kerning_values and txt2_extras_survive_a_resave_of_unchanged_text); corpus_text_layers reads real PSDs and applies their Txt2 optical kerning, but no oracle file is dedicated to kerning values
BUG-207-1 Retouch tools respect the paint target (mask, alpha channel, Quick Mask) done #207 run_stroke paints channel_cmds::target_surface; retouch_strokes_paint_the_targeted_mask (retouch_ui.rs) and engine retouch_cmds/tests.rs (e2444ce, #234)
BUG-207-2 Sample All Layers applies to Blur, Sharpen and Smudge done #207 retouch_ui.rs passes sampleAllLayers to paint.blur/sharpen/smudge; sample_all_layers_reaches_the_command (e2444ce, #234)
BUG-207-3 Smudge mixes premultiplied RGBA (no dark fringe at transparent edges) done #207 paint/src/retouch.rs premultiplies; smudge_into_transparency_leaves_no_dark_fringe (e2444ce, #234)
BUG-207-4 edit.transform transforms a targeted alpha channel done #207 edit.transform follows a targeted alpha channel or Quick Mask (explicit target since #242, Channels panel target with no target/layer param since #2835); Edit > Transform > Rotate/Flip are routed to the target too; transforms_follow_the_targeted_alpha_channel
BUG-207-5 Tests: per-tool mask/channel targeting, Sample All Layers, no smudge fringe missing #207 Only smudge_drags_colour_along_the_stroke (retouch.rs:407)
BUG-208-1 Shift while dragging a Rect/Elliptical Marquee constrains to a square/circle done #208 canvas.rs marquee_corners applies Shift via Drag::marquee_mods; marquee_tests.rs::shift_during_the_drag_draws_a_square_and_the_readout_follows covers the drag
BUG-208-2 Alt while dragging a marquee draws from the centre done #208 canvas.rs marquee_corners centres Alt drags; marquee_tests.rs::alt_draws_from_the_centre_and_shift_alt_a_centred_circle verifies rectangles and ellipses
BUG-208-3 W x H px readout beside the cursor while dragging a marquee done #208 canvas.rs marquee_readout draws the readout; marquee_tests.rs asserts dimensions while dragging and its removal after release
BUG-208-4 Add/subtract/intersect from modifiers pressed before the drag, kept by a test partial #208 Mods captured at press (canvas.rs:1679, used :1779); only a badge test (tool_feedback.rs:195)
BUG-208-5 Kittest: Shift drag gives a square, Alt drag is centred, readout matches the size done #208 crates/ui-egui/src/marquee_tests.rs covers Shift, Alt, selection bounds, W/H readout and drag completion