photocraft/docs/scorecard.md

62 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 5 11 11 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 23 16 4
Workspace and UI 6 7 10 settings that do nothing: 37 of 153 (target 0)
Type 0 3 6
Automation 3 2 6 630 of 630 menu items live (100.0%)
Reliability 5 2 0 never-crash lints on 28 of 28 crates; panic_hunt present
Distribution 4 4 3
Open bugs 22 2 8

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 missing #215 doc effects.rs:57 Contour model only
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_ui.rs:1 names/properties only; no tool, + buttons or duplicate
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 missing #216 No corpus step in ci.yml; in progress in #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 Features absent; no atomic-save failure-injection test
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 Opens the generic select.refineEdge dialog (menus.rs:308)
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 done (Preferences › Tools › Show Highlight on Rollover, #2834); box-select layers, 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) missing #214 No context menu in canvas.rs or transform_tool.rs
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) missing #214 Arbitrary rotation is a generic dialog; ruler only feeds straighten
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 still missing
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: 37 of 153 (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.always_create_smart_objects_when_placing, 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, tools.enable_flick_panning, tools.show_transformation_values, 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, units_and_rulers.print_resolution, plug_ins.show_extension_panels, plug_ins.allow_scripts_to_connect, plug_ins.generator_enabled, type_.missing_glyph_protection, type_.show_font_names_in_english, 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; other tools and text spelling menus missing
UI-217-9 UI scale presets 100/115/125/150/200 % through zoom_factor, remembered missing #217 prefs.rs:61 UiScale unused; input_tests.rs:71 asserts zoom_factor stays 1.0
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:28 five 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 missing #217 No rulers pref; doc lib.rs:523 guides are Vec
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 missing #217 Only layer.mergeVisible (edit_cmds.rs:416)
UI-217-23 Layer clipboard: copy/paste whole layers (masks, styles, type) between documents missing #217 Only copy/paste layer style (extra_cmds.rs:592)

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 missing #218 fonts.rs:28 fallback covers Arabic/Hebrew; no per-script golden 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) missing #218 Optical renders as Metrics (text.rs:21); Alt+arrow moves by word
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): 630 of 630 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 missing #220 release.yml:113-114 only x86_64 and i686 msvc
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); no RTL layout yet
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 partial #205 Implemented at transform_cmds.rs:127; no raster-mask test
BUG-205-5 Tests: linked mask + selection stays aligned; unlinked stays; targeted mask only missing #205 Unlinked case honoured (m.linked check, l.128) but untested; the other two not implemented
BUG-206-1 CharStyle models per-character manual kerning separately from the auto-kern mode missing #206 doc/text.rs:76 only kerning: Kerning enum (Metrics/Optical/Off)
BUG-206-2 PSD import keeps Optical and manual Kerning values missing #206 text/psd.rs:264 maps anything not Off to Metrics; Kerning value ignored
BUG-206-3 PSD export writes the real AutoKerning mode and Kerning values missing #206 text/psd.rs:380-381 AutoKerning bool only; Kerning hard-coded 0
BUG-206-4 Alt+Left/Right at a collapsed caret adjusts pair kerning; Character panel field missing #206 type_tool.rs:896-912 only a mode combo
BUG-206-5 Tests: PSD round trip of manual and Optical kerning, a real file in the oracle corpus missing #206 No kerning round-trip test
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 missing #207 transform_cmds.rs transform() has no target handling
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