Build Photopea Premium

YESreplaces $5/mosaves $60/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

An image editor in the browser where every hard problem is a resource problem: a layered document larger than the tab's memory, a filter that must not block the interface, and an undo history that cannot hold twenty full-resolution copies.

estimated effort multi-daythe files for this build are in the project pack

CompositingTiled rendering at the current viewport and zoomMemoryTiles evicted under pressure and recomputed from layer dataProcessingFilters in workers, never on the interface threadHistoryOperation-based undo, not full snapshotsFormatA layered format parser with explicit unsupported-feature reporting

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Builds the application and runs the test suite.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. have readyfree

    Why Parsing is where work gets silently destroyed, and the specification is large enough that knowing what you do not support is most of doing this responsibly.

    Get it Read the format documentation and collect real files including adjustment layers, effects and clipping masks.

    Verify Six real layered files open and their unsupported features are listed

  3. have readyfree

    Why Tiling, eviction and the memory ceiling are only meaningful against a document that genuinely does not fit, which is where naive implementations are killed by the browser.

    Get it A high resolution layered document of a few hundred megapixels, or generate one.

    Verify Compositing it whole exceeds the memory ceiling

  4. installfree

    Why Filters and compositing cannot run on the interface thread, and what is available differs between browsers enough to affect the design.

    Get it Check worker and offscreen canvas support in the browsers you intend to support before designing the pipeline.

    Verify A filter runs in a worker with the interface responsive

  5. decidefree

    Why The browser will kill the tab without warning, so the ceiling and the behaviour on approaching it are decisions rather than emergent properties.

    Get it Decide the ceiling, and decide whether approaching it degrades quality, refuses an operation or prompts to save.

    Verify A written note states the ceiling and the behaviour on approach

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- documents(id, width, height, colour_mode, bit_depth, opened_from)
- layers(id, document_id, ordinal, kind, name, opacity, blend_mode, visible, mask_ref, data_ref)
- tiles(layer_id, x, y, level, data_ref, dirty) · the unit of rendering and eviction
- operations(document_id, ordinal, kind, params_json, affected_region, at) · the undo history, as operations
- unsupported(document_id, feature, layer_ordinal, detail) · what a file contained that this cannot represent
- sessions(document_id, autosaved_at, storage_ref) · so a closed tab does not lose work
- Undo stores operations with their affected region rather than snapshots, because a snapshot history for a large document exhausts memory long before the user runs out of edits.

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.

VariableNeededExampleWhere the value comes from
MEMORY_CEILING_MBrequired1500The tab is killed without warning beyond this. Eviction targets it.
TILE_SIZErequired256The unit of rendering and eviction. Smaller is finer and more overhead.
AUTOSAVE_INTERVAL_SECONDSrequired30Also on visibility change, since a discarded tab warns about nothing.
UNDO_DEPTHrequired50Achievable because history is operations rather than snapshots.
OVERWRITE_AFTER_DROPPINGrequiredneverPresent as a constant. Silently destroying layers is the worst outcome here.
FILTER_PREVIEW_SCALErequired0.25Preview resolution. The applied result must still match its appearance.

The build, in order

  1. Opening a file and admitting what you cannot do

    A layered file opens with its unsupported features listed rather than silently lost.

    1. The structure is the point. A flattened import is an image viewer.

      Files src/parse.ts

    2. Per layer, where the user will see it.

      Files src/unsupported.ts

    3. Silently destroying work is the worst outcome in this build.

      Files src/save.tstests/no-lossy-overwrite.test.ts

    done when · tick each as it passes
    watch out
    • Flattening an adjustment layer or an effect you do not support, and then saving over the file, destroys work the person cannot recover, and they will not notice until much later. List what was not understood, and never overwrite the original when anything was dropped.
  2. Rendering more document than fits

    A document larger than available memory pans and zooms smoothly.

    1. Whole-document compositing is what kills the tab.

      Files src/tiles.ts

    2. Memory bounded by viewport rather than document size.

      Files src/eviction.ts

    3. Downscaling full resolution per frame is the slow path.

      Files src/zoom.ts

    done when · tick each as it passes
    watch out
    • Compositing the whole document into one buffer works until the document is large, at which point the tab is killed by the browser and the work is gone with no error you can catch. Tile, render the viewport, and evict, so memory is bounded by the viewport rather than by the document.
  3. Filters that do not freeze the tab

    A slow filter runs with the interface still responding.

    1. The interface thread must never do this work.

      Files src/filters/worker.ts

    2. And make sure the two look the same.

      Files src/filters/preview.ts

    3. Verified by comparison, not by intention.

      Files tests/cancel.test.ts

    done when · tick each as it passes
    watch out
    • Running a filter on the interface thread freezes the tab, and the browser eventually offers to close the unresponsive page, which is how a person loses an hour of work to a blur. Run in workers, and make cancellation leave the document exactly as it was.
  4. Undo without holding twenty copies

    Deep undo works on a large document within the memory ceiling.

    1. Snapshots exhaust memory in a handful of edits.

      Files src/history.ts

    2. Balancing memory against recomputation time.

      Files src/checkpoints.ts

    3. By comparison of the result, not by trusting the operations.

      Files tests/undo-redo.test.ts

    done when · tick each as it passes
    watch out
    • Storing a full snapshot per edit exhausts memory after a handful of operations on a large document, and the usual response is to reduce the history depth, which makes the editor frustrating. Store operations, checkpoint occasionally, and recompute, which trades a little time for a great deal of memory.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more design.