Files
photocraft/docs/scorecard.md
T
8948511eeb Large PSBs open with their pixels: decode planes past 2 GiB, plus 4-5 GB PSB fixtures (#375) (#782)
* PSD: decode planes up to 8 GiB, so large PSBs keep their pixels (#375)

MAX_DECODED_BYTES capped a single decode at 2 GiB. Real PSBs go past
that in one plane set: the merged image of a 30000x30000 RGB file is
2.7 GB, a 33000x33000 16-bit layer channel 2.2 GB. Opening such a file
gave a document with no layers (flattened files) or layers whose
channels were "treated as empty" (layered files). Saving hit the same
cap on the encode side and silently fell back to uncompressed data.

The cap is now 8 GiB on 64-bit targets and 2 GiB elsewhere, the budget
photocraft-codecs already uses for Limits::max_alloc. It stays a
backstop: the decoders bound their output by their input on their own
(RLE expands at most 64x, Raw needs every byte present). The Raw and
RLE output buffers are reserved with try_reserve_exact, so a machine
without that much memory gets an error instead of an abort.

generated with Claude Code

* PSD import: a channel that can't be decoded leaves its layer empty (#375)

The warning says the channel is "treated as empty", but the importer
filled a missing transparency channel with opaque alpha, so a layer
whose channels all failed became an opaque black rectangle over the
document. A channel that is present but undecodable now leaves the
whole layer empty (transparent), as the warning says.

tests/large_psb.rs: the damaged-channel case (PSD and PSB), and an
opt-in release test that opens a flattened 29000x29000 PSB whose
merged image decodes to 2.52 GB (16 s, 11.6 GB peak here).

generated with Claude Code

* Large-file fixtures: generate and check 4-5 GB PSBs (#375, #216)

photocraft-psd example big_psb writes five PSBs straight from the spec,
independent of this crate's writer and its limits: flattened and
layered, raw (4.4 and 4.8 GB files, data past 4 GiB) and RLE (small
files whose planes decode past 2 GiB, up to a 33000x33000 16-bit
layer). Every sample follows one formula, so photocraft-io example
big_psb_check opens a file through photocraft_io::import and checks
pixels without a reference; with SAVE set it saves as PSB and checks
the re-opened file.

Release build, this machine: all five open with correct pixels and
survive a PSB save and re-open (5-14 s per import). Before the 8 GiB
cap, three of the five opened empty or black.

Scorecard FILE-216-2 missing -> partial: no 2 GB PSD, 150-layer or
20 MP fixture yet, and nothing runs in CI.

generated with Claude Code

* psd: ZIP channels reserve fallibly and read in bounded chunks

A small ZIP channel declaring a huge decoded size could make
zip_decompress try a multi-GiB with_capacity/read_to_end and abort on
allocation failure. Reserve with try_reserve_exact/try_reserve, read
through take(expected) in 1 MiB chunks, and report LimitExceeded
instead. Tests: absurd declared sizes, past MAX_DECODED_BYTES, garbage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:59:39 -04:00

49 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 13 47 scenarios: 3 within budget, 22 over budget or failing, 15 tracked only, 6 not run, 7 not measurable yet (baseline 2026-10-05)
File compatibility 2 9 16 io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 229/309, round trip ≥ 307/309; photoshop oracles: oracle ≥ 132/256, round trip ≥ 258/256
Tools 6 14 10
Workspace and UI 0 5 18 settings that do nothing: 57 of 135 (target 0)
Type 0 3 6
Automation 3 2 6 626 of 626 menu items live (100.0%)
Reliability 4 1 2 never-crash lints on 27 of 27 crates; panic_hunt present
Distribution 4 4 3
Open bugs 12 4 16

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
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
P31 Camera Raw exposure change (proxy) ≤ 10 ms – – – – – 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

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
P30 Liquify 800 px brush on 3500×2200 at full resolution ≤ 1 ms per update the Liquify proxy is capped at 1600 px and the stroke path lives in the UI tool #211
P32 Brush release on a long stroke no hitch at mouse-up the live renderer and the release re-render are UI-side; needs a frame harness #211
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 229 307
photoshop oracles corpus/photoshop 256 132 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 partial #215 PSD Levels/Curves/Shadows-Highlights smart filters render (adjust_cmds.rs adjust_as_filter); Image › Adjustments on a smart object doesn't add one yet
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 done #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 are not written (one transparency sample)
FILE-215-8 Large and BigTIFF import with banded decode across all IFDs missing #215 codecs tiff.rs:3 'Only the first IFD is read'
FILE-215-9 SVG opens and places as shape layers or a vector Smart Object missing #215 No SVG codec or import
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 missing #215 panels.rs:954 const SWATCHES: [[u8; 3]; 40]
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 missing #215 No knockout / blend_interior / transparency_shapes in doc, compose or io
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 missing #216 corpus/tiff, raw and exr only listed by xtask corpus; no test reads them
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 missing #213 Live stroke only for Brush and Eraser (canvas.rs:1684)
TOOL-213-8 Clone Source: W/H link, drawn overlay (opacity/blend/clip/auto-hide), Alt target cursor partial #213 Panel W/H + overlay settings (preset_panels.rs:946-1015); no link, overlay never drawn
TOOL-213-9 Eyedropper comparison ring (new colour above, current below) while sampling missing #213 sample_eyedropper (canvas.rs:1552) draws no ring
TOOL-213-10 Ctrl+Alt drag resizes brush size/hardness with an on-canvas readout missing #213 Only [ ] resize (shortcuts.rs:270)
TOOL-213-11 Editable pressure curve, global and per control missing #213 No pressure curve in engine, paint or ui-egui
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 partial #213 Missing-pattern ABR test (abr_map/tests.rs:181), hardness test; no seam/black tests
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-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 partial #214 marquee_end supports shift (chrome_ui.rs:167) but canvas.rs passes false (#208)
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 missing #214 move_mods.rs covers Shift/Alt/arrows only
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 partial #214 Ratio dropdown and fields only (panels.rs:551-579)
TOOL-214-10 Pen: Alt breaks a handle, Shift 45 deg, Ctrl direct-selects, Backspace, close-drag handle missing #214 pen_down gets no modifiers (canvas.rs:1632)
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

Workspace and UI

Settings that do nothing: 57 of 135 (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.beep_when_done, general.export_clipboard, general.resize_image_during_place, general.always_create_smart_objects_when_placing, general.animated_zoom, general.zoom_resizes_windows, interface.show_channels_in_color, interface.dynamic_color_sliders, workspace.auto_collapse_icon_panels, workspace.auto_show_hidden_panels, workspace.open_documents_as_tabs, workspace.enable_floating_document_window_docking, workspace.large_tabs, workspace.enable_narrow_options_bar, tools.zoom_clicked_point_to_center, tools.enable_flick_panning, tools.vary_round_brush_hardness_on_hud, tools.show_transformation_values, tools.double_click_layer_mask_launches_select_and_mask, file_handling.image_previews, file_handling.lowercase_extension, 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, cursors.brush_preview_color, units_and_rulers.type_units, units_and_rulers.column_width, units_and_rulers.gutter, units_and_rulers.print_resolution, units_and_rulers.screen_resolution, plug_ins.show_extension_panels, plug_ins.allow_scripts_to_connect, plug_ins.generator_enabled, type_.smart_quotes, type_.missing_glyph_protection, type_.show_font_names_in_english, type_.use_esc_to_commit, type_.text_engine, type_.font_preview, type_.fill_new_type_layers_with_placeholder, type_.recent_fonts, enhanced_controls.scrubby_slider_acceleration, enhanced_controls.touch_gestures, enhanced_controls.zoom_with_trackpad_pinch, enhanced_controls.rotate_view_with_trackpad, 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
UI-217-2 Window size and dock width remembered across restarts missing #217 main.rs:46 native_options(): fixed 1440x900, centred on the main monitor (#419); dock.rs:529 snapshot omits the width
UI-217-3 File > Open starts in the last-used folder missing #217 services.rs:110 FileDialog::new() without set_directory
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 partial #217 canvas.rs: Close/Others/All context menu with unsaved prompt; overflow, Reveal, drag reorder still missing
UI-217-7 Command palette shows recently run commands on an empty query missing #217 palette.rs:83-108 fuzzy hits only
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 missing #217 new_doc_ui.rs:17 'Recent' is a static list
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 missing #217 panels.rs:1108 plain dropdown; trash is click-only (panels.rs:1213)
UI-217-15 Curves: smoothing / pencil mode, black/grey/white eyedroppers, tooltips missing #217 adjust_editors.rs:397 has none of them
UI-217-16 Hue/Saturation: Saturation and Lightness sliders get hue-following colour tracks missing #217 Only the Hue slider has a gradient
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 missing #217 ~33 hard-coded modifier glyph strings in ui-egui; menu_catalog.rs:341 one label
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.md): 626 of 626 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.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 27 of 27 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 partial engine lib.rs:336 dispatch; crash_guard.rs:21 import/export; the dispatch guard is untested
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 missing #212 try_reserve only in io/flat.rs:221; codec max_alloc limits cover decode only
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 missing #212 Only codec/raw max_alloc tests (codecs/tests/limits.rs:48)

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 missing #204 prefs.rs:196-202; no reader outside prefs.rs (see the prefs audit)
BUG-204-2 workspace.large_tabs, animated_zoom, auto_show_home_screen wired or hidden missing #204 prefs.rs:163,166,229; no reader outside prefs.rs
BUG-204-3 RAW prefs and ask_before_saving_layered_tiff read, or hidden partial #204 ask_before_saving_layered_tiff drives the TIFF Options prompt (ui-egui tiff_options_ui.rs); raw_defaults (prefs.rs:712) still has no readers
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 partial #204 The scorecard's prefs audit counts unread fields; no engine test or allowlist yet
BUG-205-1 Free Transform with a selection moves only the selected part of a linked mask missing #205 transform_cmds.rs:127-130 warps the whole linked mask even when the selection split pixels
BUG-205-2 Live transform preview draws the layer with its mask applied missing #205 transform_tool.rs:173 preview_image uses the raw surface
BUG-205-3 edit.transform honours target mask: transforms only a targeted, unlinked mask missing #205 transform_cmds.rs:214-270 never reads a target param
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) missing #207 retouch_cmds.rs:83 run_stroke always uses l.surface_mut(); no paint_target() call
BUG-207-2 Sample All Layers applies to Blur, Sharpen and Smudge missing #207 Checkbox at retouch_ui.rs:171-190 is not passed to the stroke commands
BUG-207-3 Smudge mixes premultiplied RGBA (no dark fringe at transparent edges) missing #207 No premultiply in paint/src/retouch.rs
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