Files
photocraft/docs/scorecard.md
T

51 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 3 11 13 io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 229/309, round trip ≥ 307/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256
Tools 9 14 8
Workspace and UI 0 5 18 settings that do nothing: 56 of 140 (target 0)
Type 0 3 6
Automation 3 2 6 627 of 627 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 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 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 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 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 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 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 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 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 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-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 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

Workspace and UI

Settings that do nothing: 56 of 140 (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.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): 627 of 627 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