Build Polarr
KINDAback to the verdict
A local photo editor whose hard problems are keeping the original file untouched while edits live as a replayable recipe, choosing a colour pipeline deliberately instead of inheriting the defaults, and making the preview and the export come out of the same code.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Tauri needs both, and discovering that halfway through the first render is a bad afternoon.
Get it nodejs.org and rustup.rs, then the Tauri prerequisites for your platform. open ↗
Verify
A fresh Tauri template builds and opens a window - have readyfree
Why Colour handling is untestable without at least one wide gamut file and one plain sRGB file whose profiles you already know.
Get it Export the same photograph twice from any camera application, once tagged Display P3 and once sRGB, and note which is which.
Verify
An image inspector reports a different profile for each file - have readyfree
Why Banding from an eight bit working chain is invisible on a photograph of leaves and obvious on a gradient, which is the point of having one.
Get it Generate a wide, very gradual grey or sky gradient at full resolution and keep it in the test fixtures.
Verify
The gradient renders smoothly before any adjustment is applied - decidefree
Why Deciding what you convert after seeing which images look wrong means writing a policy that matches your bugs.
Get it Write one paragraph: the working colour space, the colour type, what happens to a wide gamut import, and what the export writes.
Verify
The paragraph is in the README and the values it names are the ones in the code
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- images(id, path, sha256, bytes, width, height, source_profile, orientation_tag, imported_at, missing_since) - recipes(image_id, format_version, ops_json, updated_at) · the ordered operation list, which is the whole of the edit - ops inside a recipe(ordinal, kind, params_json, mask_id, enabled) · order matters, so order is stored rather than implied - masks(id, image_id, kind, geometry_json, feather, inverted) · geometry and parameters, never a saved pixel buffer - history(image_id, ordinal, ops_json, at) · undo that survives closing the application - exports(id, image_id, recipe_version, path, format, colour_space, bytes, at) · what was rendered, from which recipe, in which space - Nothing here can be destroyed by editing a photograph, because nothing here is the photograph.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
CATALOGUE_PATH | required | ~/.local/share/photoedit/catalogue.db | References and recipes. Rebuildable from sidecars, which is the whole idea. |
SIDECAR_SUFFIX | required | .recipe.json | Written beside each image. This is the durable copy of the edit. |
WORKING_COLOUR_SPACE | required | display-p3 | Passed to the context at creation. Named in the interface and the README. |
WORKING_COLOUR_TYPE | required | float16 | The other half of the same decision. Check what your runtime actually supports. |
PREVIEW_MAX_PIXELS | required | 4000000 | Preview render budget. The export ignores it and uses the same code. |
HISTORY_KEEP | required | 200 | Undo steps retained per image across restarts. |
The build, in order
A catalogue that only points at your photographs
Importing records a reference and a checksum and writes nothing to the original.
The checksum is how you tell moved from edited from gone.
Files
src/import.tstests/import-readonly.test.tsA thumbnail cache you can delete without losing anything is the right kind of cache.
Files
src/thumbnails.tsRun the suite against a read only library directory so a stray write fails loudly.
Files
src/files.tstests/import-readonly.test.ts
done when · tick each as it passeswatch out- Importing by copying every file into an application folder doubles the disk use of a photo library and leaves two copies that drift apart, while importing by path with no checksum turns a moved or re-saved file into a blank frame with no explanation. Store the path and the checksum, and show a missing file as missing with the path it had.
Colour you can name
The pipeline states its colour space instead of inheriting one.
Colour space and colour type are arguments, not defaults you inherit.
Files
src/render/context.tsYou cannot convert honestly from a profile you did not record.
Files
src/import.tssrc/render/colour.tsEight bits per channel through six operations is where banding appears.
Files
tests/gradient-banding.test.ts
done when · tick each as it passeswatch out- Leaving the context settings alone converts every wide gamut photograph into sRGB before the first adjustment, so the saturated reds somebody bought the camera for are clipped silently on a display that could have shown them. Set the colour space and colour type at creation, keep the source profile on the image row, and name the working space in the interface.
An edit that is a list, not a bitmap
Adjustments are ordered parameters replayed identically at any resolution.
The export path is the preview path with a different size argument.
Files
src/render/pipeline.tstests/preview-matches-export.test.tsUndo that dies at quit is a worse undo than none, because people rely on it.
Files
src/history.tsTurning an adjustment off is not the same as throwing it away.
Files
src/recipe.ts
done when · tick each as it passeswatch out- Rendering the preview with a fast approximation and the export with a separate proper path produces a file that does not match what the person approved, and they find out after it is published. Keep one implementation and two resolutions, with a test that renders one recipe at two sizes and compares the downscaled results within a stated tolerance.
Masks that are geometry
A local adjustment survives a crop, a re-export and a format change.
So a crop is a coordinate change rather than a rebuild.
Files
src/masks.tsWhatever resolution this render needs, and nothing persisted.
Files
src/render/masks.tstests/mask-edges.test.tsTwo operations sharing one mask should share one mask.
Files
src/recipe.ts
done when · tick each as it passeswatch out- Saving a mask as a rasterised alpha buffer at preview resolution gives an export with visibly blocky edges and quietly invalidates every mask the first time the image is cropped or straightened. Keep the geometry in original image coordinates and rasterise at whatever resolution the current render needs.
Getting the photographs and the edits back outproduct builder
Exports are reproducible and the catalogue can be rebuilt or abandoned.
The catalogue becomes a cache, which is the right thing for it to be.
Files
src/sidecar.tsA recovery path nobody has run is a recovery path that does not exist.
Files
src/rebuild.tstests/rebuild.test.tsFour hundred images and a closed laptop is the normal case, not the edge case.
Files
src/export/batch.ts
done when · tick each as it passeswatch out- Keeping the only copy of every recipe inside one SQLite file means a corrupt catalogue loses years of edits while every original survives perfectly, which is the most annoying possible way to lose work. Write a sidecar per image as well, and make the rebuild from sidecars a tested command rather than a paragraph in the README.
That is the whole plan for Polarr. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Video and creator effects.
- Generative retouching.
- Cloud sync and mobile applications.
- Print output and soft proofing.
- pixel-perfect professional tooling
- large template and asset libraries
- color-management edge cases
- cloud collaboration and mobile apps
- high-fidelity color, format, and export handling
- Presets as named partial recipes, which is a filter pack without any of the licensing
- A compare view against the unedited original, which costs nothing once edits are a list
- Lens and sensor corrections from published profiles, once the operation list has settled
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.