Vibecode Photopea Premium
track this build4 phases, 12 steps, beginner friendly0%Photopea Premium's solo core is compact: build a focused local browser image editor editor with import, a small set of transformations, undo, and standards-based export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on editor polish, assets, algorithms.
You are building a lean indie version of Photopea Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Photopea Premium · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Compositing | Tiled rendering at the current viewport and zoom | compositing a whole large document per frame is not affordable | | Memory | Tiles evicted under pressure and recomputed from layer data | the tab has a hard limit and exceeding it kills the document | | Processing | Filters in workers, never on the interface thread | a blocking filter freezes the tab and looks like a crash | | History | Operation-based undo, not full snapshots | twenty snapshots of a large document is many gigabytes | | Format | A layered format parser with explicit unsupported-feature reporting | silently flattening a feature you do not support corrupts someone's file | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The layered format specification and real files** · free - 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 - [ ] **A document larger than a tab's memory** · free - 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 - [ ] **Worker and offscreen rendering support** · free - 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 - [ ] **Your memory ceiling and what happens at it** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. - 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 If one of those is essential to you, that is the reason to keep paying for Photopea Premium, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Photopea Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a browser image editor that opens a large layered file without dying to replace Photopea Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Compositing: Tiled rendering at the current viewport and zoom · compositing a whole large document per frame is not affordable - Memory: Tiles evicted under pressure and recomputed from layer data · the tab has a hard limit and exceeding it kills the document - Processing: Filters in workers, never on the interface thread · a blocking filter freezes the tab and looks like a crash - History: Operation-based undo, not full snapshots · twenty snapshots of a large document is many gigabytes - Format: A layered format parser with explicit unsupported-feature reporting · silently flattening a feature you do not support corrupts someone's file ### Data model (create this before Phase 1) - 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. ### Phase 1 · Opening a file and admitting what you cannot do Build: Parse a layered format into layers, masks and blend modes. Report every feature you cannot represent, per layer. Refuse to save over the original when unsupported features were present. Trap: 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. Done when: A real layered file opens with layers, masks and blend modes preserved. Every unsupported feature is listed with the layer it was on. Saving over the original is refused when unsupported features were present, with a save-as offered instead. A file that fails to parse reports where rather than failing generically. Do not build yet: No rendering, editing, filters or history. ### Phase 2 · Rendering more document than fits Build: Tile the composite, render only the visible tiles at the current zoom level, and evict tiles under memory pressure. Recompute evicted tiles from layer data rather than losing them. Measure memory against a stated ceiling. Trap: 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. Done when: A document several times larger than the memory ceiling opens, pans and zooms smoothly. Memory use stays within the stated ceiling under continuous panning, measured. Evicted tiles recompute correctly rather than rendering blank. Zoom levels render from an appropriate tile level rather than downscaling full resolution. Do not build yet: No editing, filters or history yet. ### Phase 3 · Filters that do not freeze the tab Build: Run filters in workers over tiles, with progress and cancellation. Preview at reduced resolution and apply at full. Handle a filter cancelled halfway leaving the document unchanged. Trap: 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. Done when: A filter over a large document leaves the interface responsive with visible progress. Cancelling halfway leaves the document unchanged, verified by comparison. Preview renders at reduced resolution and the applied result matches the preview's appearance. A worker crash is recovered from without losing the document. Do not build yet: No history, saving or export yet. ### Phase 4 · Undo without holding twenty copies Build: Record operations with their parameters and affected region rather than snapshots. Undo by recomputing from the last checkpoint. Take checkpoints on a heuristic that bounds both memory and recomputation time. Trap: 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. Done when: Fifty operations of undo history are available on a large document within the memory ceiling. Undo of an expensive operation completes within a stated time using checkpoints. Redo after undo produces an identical result, asserted by comparison. History survives operations that were cancelled midway. Do not build yet: No saving, export or session recovery yet. ### Phase 5 · Not losing work when the tab closes Build: Autosave the document to local storage periodically and on visibility change. Recover on reopening. Export to layered and flat formats, and warn before any export that flattens layers. Trap: Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. Done when: A tab closed without warning recovers the document on reopening, tested by killing the tab. Autosave runs on an interval and on visibility change without blocking the interface. Layered export preserves layers and flat export warns first. A document too large to autosave is reported rather than silently unsaved. Do not build yet: Nothing after this. ### Out of scope (and why) - Feature parity with a desktop editor, which is decades of work and where the honest comparison always loses. - Colour management to a professional standard, which requires profile handling the browser makes genuinely difficult. - Raw photographic processing, which is a separate pipeline with its own demosaicing and profile problems. - Collaborative editing, which is a synchronisation project on top of every resource problem here. ### README must contain - That unsupported features are listed per layer and saving over the original is refused when anything was dropped - That rendering is tiled to the viewport, so memory is bounded by the view rather than by the document - That filters run in workers and cancellation leaves the document exactly unchanged - That undo stores operations rather than snapshots, because snapshots exhaust memory in a few edits - That autosave runs on an interval and on visibility change, since a discarded tab fires no warning ===== AGENTS.md ===== # Agent instructions · Photopea Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tiled rendering at the current viewport and zoom, Tiles evicted under pressure and recomputed from layer data, Filters in workers, never on the interface thread, Operation-based undo, not full snapshots, A layered format parser with explicit unsupported-feature reporting. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Photopea Premium 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Opening a file and admitting what you cannot do A layered file opens with its unsupported features listed rather than silently lost. ### Steps 1. Parse layers, masks and blend modes The structure is the point. A flattened import is an image viewer. Files: `src/parse.ts` 2. List every unsupported feature Per layer, where the user will see it. Files: `src/unsupported.ts` 3. Refuse to overwrite after dropping anything Silently destroying work is the worst outcome in this build. Files: `src/save.ts`, `tests/no-lossy-overwrite.test.ts` ### Done when - [ ] Layers, masks and blend modes are preserved - [ ] Unsupported features are listed per layer - [ ] Overwriting is refused when features were dropped - [ ] Parse failures report where they failed ### 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. ## Phase 2 · Rendering more document than fits A document larger than available memory pans and zooms smoothly. ### Steps 1. Tile and render the viewport only Whole-document compositing is what kills the tab. Files: `src/tiles.ts` 2. Evict under pressure and recompute Memory bounded by viewport rather than document size. Files: `src/eviction.ts` 3. Render zoom from tile levels Downscaling full resolution per frame is the slow path. Files: `src/zoom.ts` ### Done when - [ ] A document larger than memory pans smoothly - [ ] Memory stays within the ceiling under panning - [ ] Evicted tiles recompute correctly - [ ] Zoom renders from appropriate tile levels ### 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. ## Phase 3 · Filters that do not freeze the tab A slow filter runs with the interface still responding. ### Steps 1. Run filters in workers over tiles The interface thread must never do this work. Files: `src/filters/worker.ts` 2. Preview reduced, apply full And make sure the two look the same. Files: `src/filters/preview.ts` 3. Make cancellation leave nothing changed Verified by comparison, not by intention. Files: `tests/cancel.test.ts` ### Done when - [ ] The interface stays responsive during a filter - [ ] Cancelling leaves the document unchanged - [ ] Preview matches the applied result - [ ] A worker crash does not lose the document ### 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. ## Phase 4 · Undo without holding twenty copies Deep undo works on a large document within the memory ceiling. ### Steps 1. Store operations, not snapshots Snapshots exhaust memory in a handful of edits. Files: `src/history.ts` 2. Checkpoint on a bounded heuristic Balancing memory against recomputation time. Files: `src/checkpoints.ts` 3. Assert redo reproduces exactly By comparison of the result, not by trusting the operations. Files: `tests/undo-redo.test.ts` ### Done when - [ ] Fifty undo steps fit within the memory ceiling - [ ] Undo of an expensive operation completes in time - [ ] Redo reproduces an identical result - [ ] Cancelled operations do not corrupt history ### 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. ## Not in this build - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. ## After v1, if you want it - Non-destructive adjustment layers, which the operation-based history already suits - Smart object equivalents, once unsupported-feature reporting is trustworthy - A memory pressure indicator, since users currently discover the ceiling by losing a tab ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The tab is killed without warning beyond this. Eviction targets it. MEMORY_CEILING_MB=1500 # Required. The unit of rendering and eviction. Smaller is finer and more overhead. TILE_SIZE=256 # Required. Also on visibility change, since a discarded tab warns about nothing. AUTOSAVE_INTERVAL_SECONDS=30 # Required. Achievable because history is operations rather than snapshots. UNDO_DEPTH=50 # Required. Present as a constant. Silently destroying layers is the worst outcome here. OVERWRITE_AFTER_DROPPING=never # Required. Preview resolution. The applied result must still match its appearance. FILTER_PREVIEW_SCALE=0.25
You are building a lean indie version of Photopea Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Photopea Premium · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Compositing | Tiled rendering at the current viewport and zoom | compositing a whole large document per frame is not affordable | | Memory | Tiles evicted under pressure and recomputed from layer data | the tab has a hard limit and exceeding it kills the document | | Processing | Filters in workers, never on the interface thread | a blocking filter freezes the tab and looks like a crash | | History | Operation-based undo, not full snapshots | twenty snapshots of a large document is many gigabytes | | Format | A layered format parser with explicit unsupported-feature reporting | silently flattening a feature you do not support corrupts someone's file | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The layered format specification and real files** · free - 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 - [ ] **A document larger than a tab's memory** · free - 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 - [ ] **Worker and offscreen rendering support** · free - 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 - [ ] **Your memory ceiling and what happens at it** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. - 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 If one of those is essential to you, that is the reason to keep paying for Photopea Premium, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Photopea Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a browser image editor that opens a large layered file without dying to replace Photopea Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Compositing: Tiled rendering at the current viewport and zoom · compositing a whole large document per frame is not affordable - Memory: Tiles evicted under pressure and recomputed from layer data · the tab has a hard limit and exceeding it kills the document - Processing: Filters in workers, never on the interface thread · a blocking filter freezes the tab and looks like a crash - History: Operation-based undo, not full snapshots · twenty snapshots of a large document is many gigabytes - Format: A layered format parser with explicit unsupported-feature reporting · silently flattening a feature you do not support corrupts someone's file ### Data model (create this before Phase 1) - 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. ### Phase 1 · Opening a file and admitting what you cannot do Build: Parse a layered format into layers, masks and blend modes. Report every feature you cannot represent, per layer. Refuse to save over the original when unsupported features were present. Trap: 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. Done when: A real layered file opens with layers, masks and blend modes preserved. Every unsupported feature is listed with the layer it was on. Saving over the original is refused when unsupported features were present, with a save-as offered instead. A file that fails to parse reports where rather than failing generically. Do not build yet: No rendering, editing, filters or history. ### Phase 2 · Rendering more document than fits Build: Tile the composite, render only the visible tiles at the current zoom level, and evict tiles under memory pressure. Recompute evicted tiles from layer data rather than losing them. Measure memory against a stated ceiling. Trap: 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. Done when: A document several times larger than the memory ceiling opens, pans and zooms smoothly. Memory use stays within the stated ceiling under continuous panning, measured. Evicted tiles recompute correctly rather than rendering blank. Zoom levels render from an appropriate tile level rather than downscaling full resolution. Do not build yet: No editing, filters or history yet. ### Phase 3 · Filters that do not freeze the tab Build: Run filters in workers over tiles, with progress and cancellation. Preview at reduced resolution and apply at full. Handle a filter cancelled halfway leaving the document unchanged. Trap: 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. Done when: A filter over a large document leaves the interface responsive with visible progress. Cancelling halfway leaves the document unchanged, verified by comparison. Preview renders at reduced resolution and the applied result matches the preview's appearance. A worker crash is recovered from without losing the document. Do not build yet: No history, saving or export yet. ### Phase 4 · Undo without holding twenty copies Build: Record operations with their parameters and affected region rather than snapshots. Undo by recomputing from the last checkpoint. Take checkpoints on a heuristic that bounds both memory and recomputation time. Trap: 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. Done when: Fifty operations of undo history are available on a large document within the memory ceiling. Undo of an expensive operation completes within a stated time using checkpoints. Redo after undo produces an identical result, asserted by comparison. History survives operations that were cancelled midway. Do not build yet: No saving, export or session recovery yet. ### Phase 5 · Not losing work when the tab closes Build: Autosave the document to local storage periodically and on visibility change. Recover on reopening. Export to layered and flat formats, and warn before any export that flattens layers. Trap: Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. Done when: A tab closed without warning recovers the document on reopening, tested by killing the tab. Autosave runs on an interval and on visibility change without blocking the interface. Layered export preserves layers and flat export warns first. A document too large to autosave is reported rather than silently unsaved. Do not build yet: Nothing after this. ### Out of scope (and why) - Feature parity with a desktop editor, which is decades of work and where the honest comparison always loses. - Colour management to a professional standard, which requires profile handling the browser makes genuinely difficult. - Raw photographic processing, which is a separate pipeline with its own demosaicing and profile problems. - Collaborative editing, which is a synchronisation project on top of every resource problem here. ### README must contain - That unsupported features are listed per layer and saving over the original is refused when anything was dropped - That rendering is tiled to the viewport, so memory is bounded by the view rather than by the document - That filters run in workers and cancellation leaves the document exactly unchanged - That undo stores operations rather than snapshots, because snapshots exhaust memory in a few edits - That autosave runs on an interval and on visibility change, since a discarded tab fires no warning ===== AGENTS.md ===== # Agent instructions · Photopea Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tiled rendering at the current viewport and zoom, Tiles evicted under pressure and recomputed from layer data, Filters in workers, never on the interface thread, Operation-based undo, not full snapshots, A layered format parser with explicit unsupported-feature reporting. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Photopea Premium 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Opening a file and admitting what you cannot do A layered file opens with its unsupported features listed rather than silently lost. ### Steps 1. Parse layers, masks and blend modes The structure is the point. A flattened import is an image viewer. Files: `src/parse.ts` 2. List every unsupported feature Per layer, where the user will see it. Files: `src/unsupported.ts` 3. Refuse to overwrite after dropping anything Silently destroying work is the worst outcome in this build. Files: `src/save.ts`, `tests/no-lossy-overwrite.test.ts` ### Done when - [ ] Layers, masks and blend modes are preserved - [ ] Unsupported features are listed per layer - [ ] Overwriting is refused when features were dropped - [ ] Parse failures report where they failed ### 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. ## Phase 2 · Rendering more document than fits A document larger than available memory pans and zooms smoothly. ### Steps 1. Tile and render the viewport only Whole-document compositing is what kills the tab. Files: `src/tiles.ts` 2. Evict under pressure and recompute Memory bounded by viewport rather than document size. Files: `src/eviction.ts` 3. Render zoom from tile levels Downscaling full resolution per frame is the slow path. Files: `src/zoom.ts` ### Done when - [ ] A document larger than memory pans smoothly - [ ] Memory stays within the ceiling under panning - [ ] Evicted tiles recompute correctly - [ ] Zoom renders from appropriate tile levels ### 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. ## Phase 3 · Filters that do not freeze the tab A slow filter runs with the interface still responding. ### Steps 1. Run filters in workers over tiles The interface thread must never do this work. Files: `src/filters/worker.ts` 2. Preview reduced, apply full And make sure the two look the same. Files: `src/filters/preview.ts` 3. Make cancellation leave nothing changed Verified by comparison, not by intention. Files: `tests/cancel.test.ts` ### Done when - [ ] The interface stays responsive during a filter - [ ] Cancelling leaves the document unchanged - [ ] Preview matches the applied result - [ ] A worker crash does not lose the document ### 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. ## Phase 4 · Undo without holding twenty copies Deep undo works on a large document within the memory ceiling. ### Steps 1. Store operations, not snapshots Snapshots exhaust memory in a handful of edits. Files: `src/history.ts` 2. Checkpoint on a bounded heuristic Balancing memory against recomputation time. Files: `src/checkpoints.ts` 3. Assert redo reproduces exactly By comparison of the result, not by trusting the operations. Files: `tests/undo-redo.test.ts` ### Done when - [ ] Fifty undo steps fit within the memory ceiling - [ ] Undo of an expensive operation completes in time - [ ] Redo reproduces an identical result - [ ] Cancelled operations do not corrupt history ### 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. ## Not in this build - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. ## After v1, if you want it - Non-destructive adjustment layers, which the operation-based history already suits - Smart object equivalents, once unsupported-feature reporting is trustworthy - A memory pressure indicator, since users currently discover the ceiling by losing a tab ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The tab is killed without warning beyond this. Eviction targets it. MEMORY_CEILING_MB=1500 # Required. The unit of rendering and eviction. Smaller is finer and more overhead. TILE_SIZE=256 # Required. Also on visibility change, since a discarded tab warns about nothing. AUTOSAVE_INTERVAL_SECONDS=30 # Required. Achievable because history is operations rather than snapshots. UNDO_DEPTH=50 # Required. Present as a constant. Silently destroying layers is the worst outcome here. OVERWRITE_AFTER_DROPPING=never # Required. Preview resolution. The applied result must still match its appearance. FILTER_PREVIEW_SCALE=0.25
You are building a production product version of Photopea Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Photopea Premium · product brief ## Problem Photopea Premium's solo core is compact: build a focused local browser image editor editor with import, a small set of transformations, undo, and standards-based export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on editor polish, assets, algorithms. ## Product outcome A browser editor that opens a document too large for the tab, stays responsive during a slow filter, keeps fifty undo steps, and never quietly flattens a layer it did not understand. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - sample user-owned assets ## Explicit non-goals for v1 - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. - 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 ## Success criteria - Unsupported features are confirmed listed per layer - Overwriting is confirmed refused after dropping features - A document larger than memory is confirmed to pan smoothly - Cancelling a filter is confirmed to leave the document unchanged - A killed tab is confirmed to recover on reopening ===== BRIEF.md ===== # Build brief · Photopea Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a browser image editor that opens a large layered file without dying to replace Photopea Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Compositing: Tiled rendering at the current viewport and zoom · compositing a whole large document per frame is not affordable - Memory: Tiles evicted under pressure and recomputed from layer data · the tab has a hard limit and exceeding it kills the document - Processing: Filters in workers, never on the interface thread · a blocking filter freezes the tab and looks like a crash - History: Operation-based undo, not full snapshots · twenty snapshots of a large document is many gigabytes - Format: A layered format parser with explicit unsupported-feature reporting · silently flattening a feature you do not support corrupts someone's file ### Data model (create this before Phase 1) - 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. ### Phase 1 · Opening a file and admitting what you cannot do Build: Parse a layered format into layers, masks and blend modes. Report every feature you cannot represent, per layer. Refuse to save over the original when unsupported features were present. Trap: 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. Done when: A real layered file opens with layers, masks and blend modes preserved. Every unsupported feature is listed with the layer it was on. Saving over the original is refused when unsupported features were present, with a save-as offered instead. A file that fails to parse reports where rather than failing generically. Do not build yet: No rendering, editing, filters or history. ### Phase 2 · Rendering more document than fits Build: Tile the composite, render only the visible tiles at the current zoom level, and evict tiles under memory pressure. Recompute evicted tiles from layer data rather than losing them. Measure memory against a stated ceiling. Trap: 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. Done when: A document several times larger than the memory ceiling opens, pans and zooms smoothly. Memory use stays within the stated ceiling under continuous panning, measured. Evicted tiles recompute correctly rather than rendering blank. Zoom levels render from an appropriate tile level rather than downscaling full resolution. Do not build yet: No editing, filters or history yet. ### Phase 3 · Filters that do not freeze the tab Build: Run filters in workers over tiles, with progress and cancellation. Preview at reduced resolution and apply at full. Handle a filter cancelled halfway leaving the document unchanged. Trap: 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. Done when: A filter over a large document leaves the interface responsive with visible progress. Cancelling halfway leaves the document unchanged, verified by comparison. Preview renders at reduced resolution and the applied result matches the preview's appearance. A worker crash is recovered from without losing the document. Do not build yet: No history, saving or export yet. ### Phase 4 · Undo without holding twenty copies Build: Record operations with their parameters and affected region rather than snapshots. Undo by recomputing from the last checkpoint. Take checkpoints on a heuristic that bounds both memory and recomputation time. Trap: 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. Done when: Fifty operations of undo history are available on a large document within the memory ceiling. Undo of an expensive operation completes within a stated time using checkpoints. Redo after undo produces an identical result, asserted by comparison. History survives operations that were cancelled midway. Do not build yet: No saving, export or session recovery yet. ### Phase 5 · Not losing work when the tab closes Build: Autosave the document to local storage periodically and on visibility change. Recover on reopening. Export to layered and flat formats, and warn before any export that flattens layers. Trap: Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. Done when: A tab closed without warning recovers the document on reopening, tested by killing the tab. Autosave runs on an interval and on visibility change without blocking the interface. Layered export preserves layers and flat export warns first. A document too large to autosave is reported rather than silently unsaved. Do not build yet: Nothing after this. ### Out of scope (and why) - Feature parity with a desktop editor, which is decades of work and where the honest comparison always loses. - Colour management to a professional standard, which requires profile handling the browser makes genuinely difficult. - Raw photographic processing, which is a separate pipeline with its own demosaicing and profile problems. - Collaborative editing, which is a synchronisation project on top of every resource problem here. ### README must contain - That unsupported features are listed per layer and saving over the original is refused when anything was dropped - That rendering is tiled to the viewport, so memory is bounded by the view rather than by the document - That filters run in workers and cancellation leaves the document exactly unchanged - That undo stores operations rather than snapshots, because snapshots exhaust memory in a few edits - That autosave runs on an interval and on visibility change, since a discarded tab fires no warning ===== ARCHITECTURE.md ===== # Architecture · Photopea Premium ## Stack | Part | Choice | Why | | --- | --- | --- | | Compositing | Tiled rendering at the current viewport and zoom | compositing a whole large document per frame is not affordable | | Memory | Tiles evicted under pressure and recomputed from layer data | the tab has a hard limit and exceeding it kills the document | | Processing | Filters in workers, never on the interface thread | a blocking filter freezes the tab and looks like a crash | | History | Operation-based undo, not full snapshots | twenty snapshots of a large document is many gigabytes | | Format | A layered format parser with explicit unsupported-feature reporting | silently flattening a feature you do not support corrupts someone's file | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Parsing | Layer structure and unsupported reporting. | Any parser, provided unsupported features are listed rather than flattened. | | Rendering | Tiled viewport compositing with eviction. | Not swappable for whole-document compositing, which the browser kills. | | Filters | Worker execution with clean cancellation. | Any implementation, provided the interface thread never blocks. | | History | Operation-based undo with checkpoints. | Any scheme, provided it is not full snapshots. | | Autosave | Recovery from a discarded tab. | Any storage, provided it triggers on visibility change as well as an interval. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MEMORY_CEILING_MB` · required · The tab is killed without warning beyond this. Eviction targets it. - `TILE_SIZE` · required · The unit of rendering and eviction. Smaller is finer and more overhead. - `AUTOSAVE_INTERVAL_SECONDS` · required · Also on visibility change, since a discarded tab warns about nothing. - `UNDO_DEPTH` · required · Achievable because history is operations rather than snapshots. - `OVERWRITE_AFTER_DROPPING` · required · Present as a constant. Silently destroying layers is the worst outcome here. - `FILTER_PREVIEW_SCALE` · required · Preview resolution. The applied result must still match its appearance. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Photopea Premium product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tiled rendering at the current viewport and zoom, Tiles evicted under pressure and recomputed from layer data, Filters in workers, never on the interface thread, Operation-based undo, not full snapshots, A layered format parser with explicit unsupported-feature reporting. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. ===== MILESTONES.md ===== # Delivery milestones · Photopea Premium Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Opening a file and admitting what you cannot do A layered file opens with its unsupported features listed rather than silently lost. ### Steps 1. Parse layers, masks and blend modes The structure is the point. A flattened import is an image viewer. Files: `src/parse.ts` 2. List every unsupported feature Per layer, where the user will see it. Files: `src/unsupported.ts` 3. Refuse to overwrite after dropping anything Silently destroying work is the worst outcome in this build. Files: `src/save.ts`, `tests/no-lossy-overwrite.test.ts` ### Done when - [ ] Layers, masks and blend modes are preserved - [ ] Unsupported features are listed per layer - [ ] Overwriting is refused when features were dropped - [ ] Parse failures report where they failed ### 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. ## M2 · Rendering more document than fits A document larger than available memory pans and zooms smoothly. ### Steps 1. Tile and render the viewport only Whole-document compositing is what kills the tab. Files: `src/tiles.ts` 2. Evict under pressure and recompute Memory bounded by viewport rather than document size. Files: `src/eviction.ts` 3. Render zoom from tile levels Downscaling full resolution per frame is the slow path. Files: `src/zoom.ts` ### Done when - [ ] A document larger than memory pans smoothly - [ ] Memory stays within the ceiling under panning - [ ] Evicted tiles recompute correctly - [ ] Zoom renders from appropriate tile levels ### 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. ## M3 · Filters that do not freeze the tab A slow filter runs with the interface still responding. ### Steps 1. Run filters in workers over tiles The interface thread must never do this work. Files: `src/filters/worker.ts` 2. Preview reduced, apply full And make sure the two look the same. Files: `src/filters/preview.ts` 3. Make cancellation leave nothing changed Verified by comparison, not by intention. Files: `tests/cancel.test.ts` ### Done when - [ ] The interface stays responsive during a filter - [ ] Cancelling leaves the document unchanged - [ ] Preview matches the applied result - [ ] A worker crash does not lose the document ### 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. ## M4 · Undo without holding twenty copies Deep undo works on a large document within the memory ceiling. ### Steps 1. Store operations, not snapshots Snapshots exhaust memory in a handful of edits. Files: `src/history.ts` 2. Checkpoint on a bounded heuristic Balancing memory against recomputation time. Files: `src/checkpoints.ts` 3. Assert redo reproduces exactly By comparison of the result, not by trusting the operations. Files: `tests/undo-redo.test.ts` ### Done when - [ ] Fifty undo steps fit within the memory ceiling - [ ] Undo of an expensive operation completes in time - [ ] Redo reproduces an identical result - [ ] Cancelled operations do not corrupt history ### 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. ## M5 · Not losing work when the tab closes (production only) A closed or crashed tab does not cost the session. ### Steps 1. Autosave on interval and visibility change A discarded tab fires no warning. Files: `src/autosave.ts` 2. Recover on reopening Tested by killing the tab, not by closing it politely. Files: `tests/recovery.test.ts` 3. Warn before a flattening export Losing layers on export is the second most common way work disappears. Files: `src/export.ts` ### Done when - [ ] A killed tab recovers on reopening - [ ] Autosave does not block the interface - [ ] Flattening exports warn first - [ ] An unsaveable document is reported ### Watch out - Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. ===== OPERATIONS.md ===== # Operations · Photopea Premium ## Backup Documents live in the browser's local storage until exported, which is not a durable place. The honest position is to tell users to export, and to make exporting one action, rather than treating local storage as a filing system. ## Restore Restore from autosave and confirm layer structure and unsupported-feature records survived, since a recovered document that forgot what it could not represent might then be saved over an original. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Memory use against the ceiling under real editing is the metric, since exceeding it is not an error you can catch but a tab that disappears. ## Incident checklist A destroyed original file is the failure that cannot be undone, and it means an overwrite happened after features were dropped. That refusal is the single most important control in the build. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Unsupported features are confirmed listed per layer - [ ] Overwriting is confirmed refused after dropping features - [ ] A document larger than memory is confirmed to pan smoothly - [ ] Cancelling a filter is confirmed to leave the document unchanged - [ ] A killed tab is confirmed to recover on reopening ## Launch constraint Do not market omitted Photopea Premium capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The tab is killed without warning beyond this. Eviction targets it. MEMORY_CEILING_MB=1500 # Required. The unit of rendering and eviction. Smaller is finer and more overhead. TILE_SIZE=256 # Required. Also on visibility change, since a discarded tab warns about nothing. AUTOSAVE_INTERVAL_SECONDS=30 # Required. Achievable because history is operations rather than snapshots. UNDO_DEPTH=50 # Required. Present as a constant. Silently destroying layers is the worst outcome here. OVERWRITE_AFTER_DROPPING=never # Required. Preview resolution. The applied result must still match its appearance. FILTER_PREVIEW_SCALE=0.25
# Photopea Premium · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Compositing | Tiled rendering at the current viewport and zoom | compositing a whole large document per frame is not affordable | | Memory | Tiles evicted under pressure and recomputed from layer data | the tab has a hard limit and exceeding it kills the document | | Processing | Filters in workers, never on the interface thread | a blocking filter freezes the tab and looks like a crash | | History | Operation-based undo, not full snapshots | twenty snapshots of a large document is many gigabytes | | Format | A layered format parser with explicit unsupported-feature reporting | silently flattening a feature you do not support corrupts someone's file | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The layered format specification and real files** · free - 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 - [ ] **A document larger than a tab's memory** · free - 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 - [ ] **Worker and offscreen rendering support** · free - 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 - [ ] **Your memory ceiling and what happens at it** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. - 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 If one of those is essential to you, that is the reason to keep paying for Photopea Premium, and the README should say so rather than pretend.
# Build brief · Photopea Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a browser image editor that opens a large layered file without dying to replace Photopea Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Compositing: Tiled rendering at the current viewport and zoom · compositing a whole large document per frame is not affordable - Memory: Tiles evicted under pressure and recomputed from layer data · the tab has a hard limit and exceeding it kills the document - Processing: Filters in workers, never on the interface thread · a blocking filter freezes the tab and looks like a crash - History: Operation-based undo, not full snapshots · twenty snapshots of a large document is many gigabytes - Format: A layered format parser with explicit unsupported-feature reporting · silently flattening a feature you do not support corrupts someone's file ### Data model (create this before Phase 1) - 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. ### Phase 1 · Opening a file and admitting what you cannot do Build: Parse a layered format into layers, masks and blend modes. Report every feature you cannot represent, per layer. Refuse to save over the original when unsupported features were present. Trap: 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. Done when: A real layered file opens with layers, masks and blend modes preserved. Every unsupported feature is listed with the layer it was on. Saving over the original is refused when unsupported features were present, with a save-as offered instead. A file that fails to parse reports where rather than failing generically. Do not build yet: No rendering, editing, filters or history. ### Phase 2 · Rendering more document than fits Build: Tile the composite, render only the visible tiles at the current zoom level, and evict tiles under memory pressure. Recompute evicted tiles from layer data rather than losing them. Measure memory against a stated ceiling. Trap: 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. Done when: A document several times larger than the memory ceiling opens, pans and zooms smoothly. Memory use stays within the stated ceiling under continuous panning, measured. Evicted tiles recompute correctly rather than rendering blank. Zoom levels render from an appropriate tile level rather than downscaling full resolution. Do not build yet: No editing, filters or history yet. ### Phase 3 · Filters that do not freeze the tab Build: Run filters in workers over tiles, with progress and cancellation. Preview at reduced resolution and apply at full. Handle a filter cancelled halfway leaving the document unchanged. Trap: 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. Done when: A filter over a large document leaves the interface responsive with visible progress. Cancelling halfway leaves the document unchanged, verified by comparison. Preview renders at reduced resolution and the applied result matches the preview's appearance. A worker crash is recovered from without losing the document. Do not build yet: No history, saving or export yet. ### Phase 4 · Undo without holding twenty copies Build: Record operations with their parameters and affected region rather than snapshots. Undo by recomputing from the last checkpoint. Take checkpoints on a heuristic that bounds both memory and recomputation time. Trap: 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. Done when: Fifty operations of undo history are available on a large document within the memory ceiling. Undo of an expensive operation completes within a stated time using checkpoints. Redo after undo produces an identical result, asserted by comparison. History survives operations that were cancelled midway. Do not build yet: No saving, export or session recovery yet. ### Phase 5 · Not losing work when the tab closes Build: Autosave the document to local storage periodically and on visibility change. Recover on reopening. Export to layered and flat formats, and warn before any export that flattens layers. Trap: Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. Done when: A tab closed without warning recovers the document on reopening, tested by killing the tab. Autosave runs on an interval and on visibility change without blocking the interface. Layered export preserves layers and flat export warns first. A document too large to autosave is reported rather than silently unsaved. Do not build yet: Nothing after this. ### Out of scope (and why) - Feature parity with a desktop editor, which is decades of work and where the honest comparison always loses. - Colour management to a professional standard, which requires profile handling the browser makes genuinely difficult. - Raw photographic processing, which is a separate pipeline with its own demosaicing and profile problems. - Collaborative editing, which is a synchronisation project on top of every resource problem here. ### README must contain - That unsupported features are listed per layer and saving over the original is refused when anything was dropped - That rendering is tiled to the viewport, so memory is bounded by the view rather than by the document - That filters run in workers and cancellation leaves the document exactly unchanged - That undo stores operations rather than snapshots, because snapshots exhaust memory in a few edits - That autosave runs on an interval and on visibility change, since a discarded tab fires no warning
# Agent instructions · Photopea Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tiled rendering at the current viewport and zoom, Tiles evicted under pressure and recomputed from layer data, Filters in workers, never on the interface thread, Operation-based undo, not full snapshots, A layered format parser with explicit unsupported-feature reporting. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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.
# Build plan · Photopea Premium 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Opening a file and admitting what you cannot do A layered file opens with its unsupported features listed rather than silently lost. ### Steps 1. Parse layers, masks and blend modes The structure is the point. A flattened import is an image viewer. Files: `src/parse.ts` 2. List every unsupported feature Per layer, where the user will see it. Files: `src/unsupported.ts` 3. Refuse to overwrite after dropping anything Silently destroying work is the worst outcome in this build. Files: `src/save.ts`, `tests/no-lossy-overwrite.test.ts` ### Done when - [ ] Layers, masks and blend modes are preserved - [ ] Unsupported features are listed per layer - [ ] Overwriting is refused when features were dropped - [ ] Parse failures report where they failed ### 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. ## Phase 2 · Rendering more document than fits A document larger than available memory pans and zooms smoothly. ### Steps 1. Tile and render the viewport only Whole-document compositing is what kills the tab. Files: `src/tiles.ts` 2. Evict under pressure and recompute Memory bounded by viewport rather than document size. Files: `src/eviction.ts` 3. Render zoom from tile levels Downscaling full resolution per frame is the slow path. Files: `src/zoom.ts` ### Done when - [ ] A document larger than memory pans smoothly - [ ] Memory stays within the ceiling under panning - [ ] Evicted tiles recompute correctly - [ ] Zoom renders from appropriate tile levels ### 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. ## Phase 3 · Filters that do not freeze the tab A slow filter runs with the interface still responding. ### Steps 1. Run filters in workers over tiles The interface thread must never do this work. Files: `src/filters/worker.ts` 2. Preview reduced, apply full And make sure the two look the same. Files: `src/filters/preview.ts` 3. Make cancellation leave nothing changed Verified by comparison, not by intention. Files: `tests/cancel.test.ts` ### Done when - [ ] The interface stays responsive during a filter - [ ] Cancelling leaves the document unchanged - [ ] Preview matches the applied result - [ ] A worker crash does not lose the document ### 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. ## Phase 4 · Undo without holding twenty copies Deep undo works on a large document within the memory ceiling. ### Steps 1. Store operations, not snapshots Snapshots exhaust memory in a handful of edits. Files: `src/history.ts` 2. Checkpoint on a bounded heuristic Balancing memory against recomputation time. Files: `src/checkpoints.ts` 3. Assert redo reproduces exactly By comparison of the result, not by trusting the operations. Files: `tests/undo-redo.test.ts` ### Done when - [ ] Fifty undo steps fit within the memory ceiling - [ ] Undo of an expensive operation completes in time - [ ] Redo reproduces an identical result - [ ] Cancelled operations do not corrupt history ### 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. ## Not in this build - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. ## After v1, if you want it - Non-destructive adjustment layers, which the operation-based history already suits - Smart object equivalents, once unsupported-feature reporting is trustworthy - A memory pressure indicator, since users currently discover the ceiling by losing a tab
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The tab is killed without warning beyond this. Eviction targets it. MEMORY_CEILING_MB=1500 # Required. The unit of rendering and eviction. Smaller is finer and more overhead. TILE_SIZE=256 # Required. Also on visibility change, since a discarded tab warns about nothing. AUTOSAVE_INTERVAL_SECONDS=30 # Required. Achievable because history is operations rather than snapshots. UNDO_DEPTH=50 # Required. Present as a constant. Silently destroying layers is the worst outcome here. OVERWRITE_AFTER_DROPPING=never # Required. Preview resolution. The applied result must still match its appearance. FILTER_PREVIEW_SCALE=0.25
# Photopea Premium · product brief ## Problem Photopea Premium's solo core is compact: build a focused local browser image editor editor with import, a small set of transformations, undo, and standards-based export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on editor polish, assets, algorithms. ## Product outcome A browser editor that opens a document too large for the tab, stays responsive during a slow filter, keeps fifty undo steps, and never quietly flattens a layer it did not understand. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - sample user-owned assets ## Explicit non-goals for v1 - Desktop editor feature parity. - Professional colour management. - Raw photographic processing. - Collaborative editing. - 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 ## Success criteria - Unsupported features are confirmed listed per layer - Overwriting is confirmed refused after dropping features - A document larger than memory is confirmed to pan smoothly - Cancelling a filter is confirmed to leave the document unchanged - A killed tab is confirmed to recover on reopening
# Build brief · Photopea Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a browser image editor that opens a large layered file without dying to replace Photopea Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Compositing: Tiled rendering at the current viewport and zoom · compositing a whole large document per frame is not affordable - Memory: Tiles evicted under pressure and recomputed from layer data · the tab has a hard limit and exceeding it kills the document - Processing: Filters in workers, never on the interface thread · a blocking filter freezes the tab and looks like a crash - History: Operation-based undo, not full snapshots · twenty snapshots of a large document is many gigabytes - Format: A layered format parser with explicit unsupported-feature reporting · silently flattening a feature you do not support corrupts someone's file ### Data model (create this before Phase 1) - 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. ### Phase 1 · Opening a file and admitting what you cannot do Build: Parse a layered format into layers, masks and blend modes. Report every feature you cannot represent, per layer. Refuse to save over the original when unsupported features were present. Trap: 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. Done when: A real layered file opens with layers, masks and blend modes preserved. Every unsupported feature is listed with the layer it was on. Saving over the original is refused when unsupported features were present, with a save-as offered instead. A file that fails to parse reports where rather than failing generically. Do not build yet: No rendering, editing, filters or history. ### Phase 2 · Rendering more document than fits Build: Tile the composite, render only the visible tiles at the current zoom level, and evict tiles under memory pressure. Recompute evicted tiles from layer data rather than losing them. Measure memory against a stated ceiling. Trap: 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. Done when: A document several times larger than the memory ceiling opens, pans and zooms smoothly. Memory use stays within the stated ceiling under continuous panning, measured. Evicted tiles recompute correctly rather than rendering blank. Zoom levels render from an appropriate tile level rather than downscaling full resolution. Do not build yet: No editing, filters or history yet. ### Phase 3 · Filters that do not freeze the tab Build: Run filters in workers over tiles, with progress and cancellation. Preview at reduced resolution and apply at full. Handle a filter cancelled halfway leaving the document unchanged. Trap: 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. Done when: A filter over a large document leaves the interface responsive with visible progress. Cancelling halfway leaves the document unchanged, verified by comparison. Preview renders at reduced resolution and the applied result matches the preview's appearance. A worker crash is recovered from without losing the document. Do not build yet: No history, saving or export yet. ### Phase 4 · Undo without holding twenty copies Build: Record operations with their parameters and affected region rather than snapshots. Undo by recomputing from the last checkpoint. Take checkpoints on a heuristic that bounds both memory and recomputation time. Trap: 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. Done when: Fifty operations of undo history are available on a large document within the memory ceiling. Undo of an expensive operation completes within a stated time using checkpoints. Redo after undo produces an identical result, asserted by comparison. History survives operations that were cancelled midway. Do not build yet: No saving, export or session recovery yet. ### Phase 5 · Not losing work when the tab closes Build: Autosave the document to local storage periodically and on visibility change. Recover on reopening. Export to layered and flat formats, and warn before any export that flattens layers. Trap: Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening. Done when: A tab closed without warning recovers the document on reopening, tested by killing the tab. Autosave runs on an interval and on visibility change without blocking the interface. Layered export preserves layers and flat export warns first. A document too large to autosave is reported rather than silently unsaved. Do not build yet: Nothing after this. ### Out of scope (and why) - Feature parity with a desktop editor, which is decades of work and where the honest comparison always loses. - Colour management to a professional standard, which requires profile handling the browser makes genuinely difficult. - Raw photographic processing, which is a separate pipeline with its own demosaicing and profile problems. - Collaborative editing, which is a synchronisation project on top of every resource problem here. ### README must contain - That unsupported features are listed per layer and saving over the original is refused when anything was dropped - That rendering is tiled to the viewport, so memory is bounded by the view rather than by the document - That filters run in workers and cancellation leaves the document exactly unchanged - That undo stores operations rather than snapshots, because snapshots exhaust memory in a few edits - That autosave runs on an interval and on visibility change, since a discarded tab fires no warning
# Architecture · Photopea Premium ## Stack | Part | Choice | Why | | --- | --- | --- | | Compositing | Tiled rendering at the current viewport and zoom | compositing a whole large document per frame is not affordable | | Memory | Tiles evicted under pressure and recomputed from layer data | the tab has a hard limit and exceeding it kills the document | | Processing | Filters in workers, never on the interface thread | a blocking filter freezes the tab and looks like a crash | | History | Operation-based undo, not full snapshots | twenty snapshots of a large document is many gigabytes | | Format | A layered format parser with explicit unsupported-feature reporting | silently flattening a feature you do not support corrupts someone's file | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Parsing | Layer structure and unsupported reporting. | Any parser, provided unsupported features are listed rather than flattened. | | Rendering | Tiled viewport compositing with eviction. | Not swappable for whole-document compositing, which the browser kills. | | Filters | Worker execution with clean cancellation. | Any implementation, provided the interface thread never blocks. | | History | Operation-based undo with checkpoints. | Any scheme, provided it is not full snapshots. | | Autosave | Recovery from a discarded tab. | Any storage, provided it triggers on visibility change as well as an interval. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MEMORY_CEILING_MB` · required · The tab is killed without warning beyond this. Eviction targets it. - `TILE_SIZE` · required · The unit of rendering and eviction. Smaller is finer and more overhead. - `AUTOSAVE_INTERVAL_SECONDS` · required · Also on visibility change, since a discarded tab warns about nothing. - `UNDO_DEPTH` · required · Achievable because history is operations rather than snapshots. - `OVERWRITE_AFTER_DROPPING` · required · Present as a constant. Silently destroying layers is the worst outcome here. - `FILTER_PREVIEW_SCALE` · required · Preview resolution. The applied result must still match its appearance. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Photopea Premium product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tiled rendering at the current viewport and zoom, Tiles evicted under pressure and recomputed from layer data, Filters in workers, never on the interface thread, Operation-based undo, not full snapshots, A layered format parser with explicit unsupported-feature reporting. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening.
# Delivery milestones · Photopea Premium Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Opening a file and admitting what you cannot do A layered file opens with its unsupported features listed rather than silently lost. ### Steps 1. Parse layers, masks and blend modes The structure is the point. A flattened import is an image viewer. Files: `src/parse.ts` 2. List every unsupported feature Per layer, where the user will see it. Files: `src/unsupported.ts` 3. Refuse to overwrite after dropping anything Silently destroying work is the worst outcome in this build. Files: `src/save.ts`, `tests/no-lossy-overwrite.test.ts` ### Done when - [ ] Layers, masks and blend modes are preserved - [ ] Unsupported features are listed per layer - [ ] Overwriting is refused when features were dropped - [ ] Parse failures report where they failed ### 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. ## M2 · Rendering more document than fits A document larger than available memory pans and zooms smoothly. ### Steps 1. Tile and render the viewport only Whole-document compositing is what kills the tab. Files: `src/tiles.ts` 2. Evict under pressure and recompute Memory bounded by viewport rather than document size. Files: `src/eviction.ts` 3. Render zoom from tile levels Downscaling full resolution per frame is the slow path. Files: `src/zoom.ts` ### Done when - [ ] A document larger than memory pans smoothly - [ ] Memory stays within the ceiling under panning - [ ] Evicted tiles recompute correctly - [ ] Zoom renders from appropriate tile levels ### 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. ## M3 · Filters that do not freeze the tab A slow filter runs with the interface still responding. ### Steps 1. Run filters in workers over tiles The interface thread must never do this work. Files: `src/filters/worker.ts` 2. Preview reduced, apply full And make sure the two look the same. Files: `src/filters/preview.ts` 3. Make cancellation leave nothing changed Verified by comparison, not by intention. Files: `tests/cancel.test.ts` ### Done when - [ ] The interface stays responsive during a filter - [ ] Cancelling leaves the document unchanged - [ ] Preview matches the applied result - [ ] A worker crash does not lose the document ### 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. ## M4 · Undo without holding twenty copies Deep undo works on a large document within the memory ceiling. ### Steps 1. Store operations, not snapshots Snapshots exhaust memory in a handful of edits. Files: `src/history.ts` 2. Checkpoint on a bounded heuristic Balancing memory against recomputation time. Files: `src/checkpoints.ts` 3. Assert redo reproduces exactly By comparison of the result, not by trusting the operations. Files: `tests/undo-redo.test.ts` ### Done when - [ ] Fifty undo steps fit within the memory ceiling - [ ] Undo of an expensive operation completes in time - [ ] Redo reproduces an identical result - [ ] Cancelled operations do not corrupt history ### 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. ## M5 · Not losing work when the tab closes (production only) A closed or crashed tab does not cost the session. ### Steps 1. Autosave on interval and visibility change A discarded tab fires no warning. Files: `src/autosave.ts` 2. Recover on reopening Tested by killing the tab, not by closing it politely. Files: `tests/recovery.test.ts` 3. Warn before a flattening export Losing layers on export is the second most common way work disappears. Files: `src/export.ts` ### Done when - [ ] A killed tab recovers on reopening - [ ] Autosave does not block the interface - [ ] Flattening exports warn first - [ ] An unsaveable document is reported ### Watch out - Relying on the browser's warning before closing means a crash, a tab discarded under memory pressure, or a mobile browser reclaiming the page loses everything, and none of those fire that warning. Autosave to local storage on an interval and on visibility change, and recover on reopening.
# Operations · Photopea Premium ## Backup Documents live in the browser's local storage until exported, which is not a durable place. The honest position is to tell users to export, and to make exporting one action, rather than treating local storage as a filing system. ## Restore Restore from autosave and confirm layer structure and unsupported-feature records survived, since a recovered document that forgot what it could not represent might then be saved over an original. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Memory use against the ceiling under real editing is the metric, since exceeding it is not an error you can catch but a tab that disappears. ## Incident checklist A destroyed original file is the failure that cannot be undone, and it means an overwrite happened after features were dropped. That refusal is the single most important control in the build. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Unsupported features are confirmed listed per layer - [ ] Overwriting is confirmed refused after dropping features - [ ] A document larger than memory is confirmed to pan smoothly - [ ] Cancelling a filter is confirmed to leave the document unchanged - [ ] A killed tab is confirmed to recover on reopening ## Launch constraint Do not market omitted Photopea Premium capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The tab is killed without warning beyond this. Eviction targets it. MEMORY_CEILING_MB=1500 # Required. The unit of rendering and eviction. Smaller is finer and more overhead. TILE_SIZE=256 # Required. Also on visibility change, since a discarded tab warns about nothing. AUTOSAVE_INTERVAL_SECONDS=30 # Required. Achievable because history is operations rather than snapshots. UNDO_DEPTH=50 # Required. Present as a constant. Silently destroying layers is the worst outcome here. OVERWRITE_AFTER_DROPPING=never # Required. Preview resolution. The applied result must still match its appearance. FILTER_PREVIEW_SCALE=0.25
$ choose a build depth, inspect the files, then open the complete pack in your agent
Photopea Premium: Creative users pay for speed, output fidelity, nondestructive editing, and years of edge-case work hidden behind direct manipulation.
xpixel-perfect professional tooling
xlarge template and asset libraries
xcolor-management edge cases
xcloud collaboration and mobile apps
xhigh-fidelity color, format, and export handling
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Vibecode Photopea Premium
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Photopea Premium replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Photopea Premium cost?
Photopea Premium costs about $5/month (Premium, checked 2026-07-31), which is $60 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Photopea Premium?
Honestly: 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. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Photopea Premium?
Yes: Affinity (A free professional desktop suite for raster, vector, and layout work with PSD handling; no browser tab, no subscription.) Photopea Free (The same editor, the same PSD machinery, and real exports; Premium mainly removes ads and adds convenience rather than unlocking the core.) The prompt is for when you want it exactly your way.