Vibecode EdrawMax
track this build4 phases, 12 steps, beginner friendly0%The visible general diagramming loop is buildable, but a credible replacement needs more than the first screen. EdrawMax earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of EdrawMax. 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 ===== # EdrawMax · indie build A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. 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 | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server at all | a diagram editor that needs a backend to draw a rectangle has chosen wrong | | Canvas | SVG for the document, with hit testing kept off the render path | SVG gives you text, selection and export in the same representation | | Persistence | Files the user chose, with an indexed database used only as a working cache | browser storage is evictable and the published eviction rules are why | | History | A command stack where every mutation carries its inverse | undo built by snapshotting is slow and undo built by hand is incomplete | | Export | SVG carrying the source document as metadata, plus raster at a chosen scale | an export that cannot be reopened is a screenshot | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the development server, the build and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Two browsers, one with a file system interface and one without** · free - Why: The persistence design exists because these behave differently, and testing in only one of them guarantees you ship the fallback untested. - Get it: Any two current browsers from different engine families. - Verify: Save and reopen work in both, by the intended path in each - [ ] **What the browser cache is allowed to be the only copy of** · free - Why: This is the decision the whole build hangs on, and making it implicitly means making it wrong. - Get it: Write down that the cache covers unsaved changes since the last file write and nothing else. - Verify: The statement appears in the README and a test clears storage and loses nothing saved - [ ] **A real diagram you already need** · free - Why: Connector routing and text sizing only misbehave on real content, and a demo of four boxes will never show you the problem. - Get it: Rebuild an architecture or process diagram you actually use. - Verify: It renders, exports and reopens with labels intact ## 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: - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments If one of those is essential to you, that is the reason to keep paying for EdrawMax, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · EdrawMax 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 diagramming editor whose documents live in files you chose, with connectors that stay attached and an export you can reopen to replace EdrawMax. 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. ### Rule zero Browser storage is a cache. It is not a place to leave a diagram, and the reason is published. When WebKit announced full third-party cookie blocking on 24 March 2020 it also described a seven-day cap on script-writable storage: after seven days of Safari use without user interaction on the site, indexed databases, local storage, session storage, media keys and service worker registrations and their caches are deleted. Interaction resets the counter, and a web application added to the home screen is counted separately. Check whether that behaviour still stands in the browsers you care about before you decide either way, because the direction of travel across browsers is towards eviction rather than away from it. An editor whose only copy of your diagram is an indexed database is an editor that loses the work of somebody who took a fortnight off. So: the browser holds a working cache, and every document also lives in a real file the user chose, written through the file system interface where the browser has one and through an explicit download and reopen where it does not. The interface states, where you can see it without looking, when this document last reached a file, and that indicator goes stale loudly. The corollary: the export is the format. If a diagram cannot be reopened from the thing you exported, you made a picture rather than a document, and you should say so in the dialogue. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server at all · a diagram editor that needs a backend to draw a rectangle has chosen wrong - Canvas: SVG for the document, with hit testing kept off the render path · SVG gives you text, selection and export in the same representation - Persistence: Files the user chose, with an indexed database used only as a working cache · browser storage is evictable and the published eviction rules are why - History: A command stack where every mutation carries its inverse · undo built by snapshotting is slow and undo built by hand is incomplete - Export: SVG carrying the source document as metadata, plus raster at a chosen scale · an export that cannot be reopened is a screenshot ### Data model (create this before Phase 1) - the document file: format_version, pages, shapes, connectors, styles · one JSON object, readable without the editor - pages(id, name, ordinal, width, height, grid) · a document is pages, and a single page is a document with one - shapes(id, page_id, kind, x, y, w, h, rotation, style_ref, text, ports_json) · ports are named points, because connectors attach to names rather than coordinates - connectors(id, page_id, from_shape, from_port, to_shape, to_port, waypoints_json, routing, style_ref) · never two absolute points - styles(id, name, declarations_json) · named, so changing the look of every process box is one edit - file_state(handle_ref, last_written_at, last_written_hash, dirty) · surfaced in the interface rather than living in a variable - commands: an in-memory stack of do and undo pairs; nothing in the application mutates a document outside one. ### Phase 1 · A file on disk within the first minute Build: Open, save and save-as through the browser's file interface where available, with an explicit download and reopen path where it is not. Keep the indexed database as a cache keyed by the file handle. Show the time since the document last reached a file. On load, if the cache and the file disagree, present both and let the user choose. Trap: Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. Trap: When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. Done when: Clearing all browser storage loses nothing that had been written to a file, tested by clearing and reopening. The interface displays the time since the last file write and shows a distinct state once it exceeds a configured threshold. A cache newer than the file produces a choice on open and neither copy is discarded, tested by editing then killing the tab. A new document prompts for a file within a configured time and does not let itself be forgotten. Do not build yet: No connectors, undo stack, export or library view yet. ### Phase 2 · Connectors that stay attached Build: Give shapes named ports. Store a connector as source shape and port, target shape and port, plus waypoints held relative to those anchors. Re-route orthogonally when a shape moves, keeping the waypoints that remain geometrically sensible and telling the user when one had to be dropped. Deleting a shape either deletes its connectors or leaves them visibly orphaned, never dangling. Trap: Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. Done when: Moving a shape moves every attached connector endpoint, tested across a multi-connector graph. Deleting a shape leaves no connector referencing a missing identifier, asserted by a document validation run in the suite. A manually bent connector keeps its bends through a small move and reports in the interface when a bend was discarded, tested. Orthogonal routing does not pass through the interiors of other shapes on a generated test layout. Do not build yet: No undo stack, export or library view yet. ### Phase 3 · Undo that is provably complete Build: Route every document mutation through a command object carrying its inverse. Group multi-select operations into one command. Restore selection as part of undo. Add a property test that applies a long random sequence of commands, undoes all of them, and compares the serialised document byte for byte against the starting state. Trap: Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. Done when: A property test applying at least a few hundred random operations and undoing all of them produces a document byte-identical to the start, run in the suite. Every registered command type has an inverse, asserted by a test over the registry. Redo after undo reproduces the state exactly including selection, tested. No code path mutates a document outside a command, checked by review and by a test that the mutation helpers are unexported. Do not build yet: No export, library view or performance work yet. ### Phase 4 · An export you can reopen Build: Export SVG with the source document embedded as metadata so opening that file in the editor gives back the diagram. Offer a font choice in the dialogue: reference the font and state the dependency, or convert text to outlines and state that it becomes uneditable and unsearchable. Export raster at a chosen scale with the resulting pixel dimensions shown before export. Trap: An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. Done when: An exported SVG reopened in the editor produces a document identical to the original, tested by round trip. The export dialogue states the font consequence for each option in words, checked by review. A raster export at a chosen scale has exactly the pixel dimensions shown beforehand, tested. Export completes with the network disabled, tested by running it offline. Do not build yet: No library view, performance budget or crash recovery work yet. ### Phase 5 · Many diagrams, and a canvas that stays usable Build: A library view over a directory the user granted, listing documents with their last write times. A generated large document for measurement, a stated shape count the editor must stay interactive at, and hit testing kept off the render path. Crash recovery from the cache with the divergence choice from Phase 1. A bulk export of a whole folder. Trap: A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. Done when: A generated document at the stated shape count stays interactive while dragging, measured with the frame timings recorded in the test output. The library view lists documents from a granted directory with their last write times and opens one without a full parse of the others. Recovering from the cache after a killed tab offers the same divergence choice as a normal open, tested. Bulk export writes one file per document and reports failures individually rather than aborting. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer cursors and comments, which is a conflict resolution problem and is most of what the paid tier sells. - High-fidelity import from proprietary diagram formats, which is reverse engineering a moving target and never finishes. - A large template and shape library, which is a content business requiring maintenance for every template forever. - Presentation mode and slide export, which is a second application wearing the same interface. ### README must contain - That browser storage is a cache and can be evicted, with the published eviction behaviour named and dated, and what that means for where your diagrams live - How to tell, at a glance, when the current document last reached a file - That connectors are anchored to shapes and ports rather than to coordinates, and what happens to manual bends on a move - The font trade-off in SVG export, in both directions, and that the source document is embedded in the exported file - The shape count the editor is measured against and where that measurement lives in the test suite ===== AGENTS.md ===== # Agent instructions · EdrawMax indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server at all, SVG for the document, with hit testing kept off the render path, Files the user chose, with an indexed database used only as a working cache, A command stack where every mutation carries its inverse, SVG carrying the source document as metadata, plus raster at a chosen scale. 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 - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ===== BUILD_PLAN.md ===== # Build plan · EdrawMax A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A file on disk within the first minute Every document has somewhere real to live and the interface says when it last got there. ### Steps 1. Make the file the document and the cache a cache One of them can be evicted and it is not the file. Files: `src/persist/file.ts`, `src/persist/cache.ts` 2. Show time since the last file write The number that tells you whether your work exists. Files: `src/ui/SaveState.tsx` 3. Detect divergence with a hash and offer both Never resolve somebody else's conflict for them. Files: `src/persist/reconcile.ts`, `tests/reconcile.test.ts` ### Done when - [ ] Clearing browser storage loses nothing saved to a file - [ ] Time since last file write is visible and goes stale - [ ] A cache and file divergence offers both copies - [ ] A new document is prompted for a file quickly ### Watch out - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. ## Phase 2 · Connectors that stay attached Moving a box moves everything joined to it, and manual bends survive. ### Steps 1. Anchor to shape and port, never to coordinates The identifier survives the move; the coordinate does not. Files: `src/model/connectors.ts`, `tests/connectors.test.ts` 2. Keep waypoints relative to the anchors So a small move does not erase somebody's work. Files: `src/routing/waypoints.ts` 3. Validate the document for dangling references Run it in the suite after every mutating operation. Files: `src/model/validate.ts`, `tests/validate.test.ts` ### Done when - [ ] Moving a shape moves its connector endpoints - [ ] No connector references a deleted shape - [ ] Manual bends survive a small move or are reported - [ ] Routing avoids other shapes' interiors ### Watch out - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. ## Phase 3 · Undo that is provably complete Any sequence of operations, fully undone, returns the document exactly as it was. ### Steps 1. Give every mutation a command with an inverse And assert in a test that none is missing one. Files: `src/history/commands.ts`, `tests/inverses.test.ts` 2. Write the property test early, not last It is the cheapest way to find the operation you forgot. Files: `tests/undo-property.test.ts` 3. Restore selection as part of undo Undo that loses your selection makes you check what happened. Files: `src/history/stack.ts` ### Done when - [ ] A long random sequence undoes to a byte-identical document - [ ] Every command type has an inverse - [ ] Redo reproduces state and selection - [ ] Nothing mutates a document outside a command ### Watch out - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. ## Phase 4 · An export you can reopen The SVG that goes to a colleague carries the document that made it. ### Steps 1. Embed the source document in the SVG The picture and the document should be one file. Files: `src/export/svg.ts`, `tests/export-roundtrip.test.ts` 2. Put the font trade-off in the dialogue Both consequences, in words, at the moment of choosing. Files: `src/ui/ExportDialog.tsx` 3. Show the pixel dimensions before exporting Nobody wants to guess what scale four means. Files: `src/export/raster.ts` ### Done when - [ ] An exported SVG reopens to an identical document - [ ] The dialogue states the font consequence - [ ] Raster dimensions match what was shown - [ ] Export works offline ### Watch out - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ## Not in this build - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. ## After v1, if you want it - Automatic layout for a graph you paste in, which is a solved algorithm and a large quality jump - A text format for the document that reads well in a diff, so diagrams can live beside code - Shape libraries loaded from a file, once the document format has stopped changing ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The indexed database used for crash recovery only. Evictable by design. CACHE_DB_NAME=diagram-cache # Required. How long a new document may go without a file before the editor insists. SAVE_PROMPT_SECONDS=120 # Required. After this long without a file write, the indicator changes state visibly. STALE_SAVE_WARN_MINUTES=10 # Required. The size the performance test generates and the README promises. TARGET_SHAPE_COUNT=500 # Required. Default raster scale. The dialogue shows resulting pixel dimensions. EXPORT_SCALE_DEFAULT=2
You are building a lean indie version of EdrawMax. 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 ===== # EdrawMax · indie build A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. 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 | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server at all | a diagram editor that needs a backend to draw a rectangle has chosen wrong | | Canvas | SVG for the document, with hit testing kept off the render path | SVG gives you text, selection and export in the same representation | | Persistence | Files the user chose, with an indexed database used only as a working cache | browser storage is evictable and the published eviction rules are why | | History | A command stack where every mutation carries its inverse | undo built by snapshotting is slow and undo built by hand is incomplete | | Export | SVG carrying the source document as metadata, plus raster at a chosen scale | an export that cannot be reopened is a screenshot | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the development server, the build and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Two browsers, one with a file system interface and one without** · free - Why: The persistence design exists because these behave differently, and testing in only one of them guarantees you ship the fallback untested. - Get it: Any two current browsers from different engine families. - Verify: Save and reopen work in both, by the intended path in each - [ ] **What the browser cache is allowed to be the only copy of** · free - Why: This is the decision the whole build hangs on, and making it implicitly means making it wrong. - Get it: Write down that the cache covers unsaved changes since the last file write and nothing else. - Verify: The statement appears in the README and a test clears storage and loses nothing saved - [ ] **A real diagram you already need** · free - Why: Connector routing and text sizing only misbehave on real content, and a demo of four boxes will never show you the problem. - Get it: Rebuild an architecture or process diagram you actually use. - Verify: It renders, exports and reopens with labels intact ## 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: - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments If one of those is essential to you, that is the reason to keep paying for EdrawMax, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · EdrawMax 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 diagramming editor whose documents live in files you chose, with connectors that stay attached and an export you can reopen to replace EdrawMax. 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. ### Rule zero Browser storage is a cache. It is not a place to leave a diagram, and the reason is published. When WebKit announced full third-party cookie blocking on 24 March 2020 it also described a seven-day cap on script-writable storage: after seven days of Safari use without user interaction on the site, indexed databases, local storage, session storage, media keys and service worker registrations and their caches are deleted. Interaction resets the counter, and a web application added to the home screen is counted separately. Check whether that behaviour still stands in the browsers you care about before you decide either way, because the direction of travel across browsers is towards eviction rather than away from it. An editor whose only copy of your diagram is an indexed database is an editor that loses the work of somebody who took a fortnight off. So: the browser holds a working cache, and every document also lives in a real file the user chose, written through the file system interface where the browser has one and through an explicit download and reopen where it does not. The interface states, where you can see it without looking, when this document last reached a file, and that indicator goes stale loudly. The corollary: the export is the format. If a diagram cannot be reopened from the thing you exported, you made a picture rather than a document, and you should say so in the dialogue. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server at all · a diagram editor that needs a backend to draw a rectangle has chosen wrong - Canvas: SVG for the document, with hit testing kept off the render path · SVG gives you text, selection and export in the same representation - Persistence: Files the user chose, with an indexed database used only as a working cache · browser storage is evictable and the published eviction rules are why - History: A command stack where every mutation carries its inverse · undo built by snapshotting is slow and undo built by hand is incomplete - Export: SVG carrying the source document as metadata, plus raster at a chosen scale · an export that cannot be reopened is a screenshot ### Data model (create this before Phase 1) - the document file: format_version, pages, shapes, connectors, styles · one JSON object, readable without the editor - pages(id, name, ordinal, width, height, grid) · a document is pages, and a single page is a document with one - shapes(id, page_id, kind, x, y, w, h, rotation, style_ref, text, ports_json) · ports are named points, because connectors attach to names rather than coordinates - connectors(id, page_id, from_shape, from_port, to_shape, to_port, waypoints_json, routing, style_ref) · never two absolute points - styles(id, name, declarations_json) · named, so changing the look of every process box is one edit - file_state(handle_ref, last_written_at, last_written_hash, dirty) · surfaced in the interface rather than living in a variable - commands: an in-memory stack of do and undo pairs; nothing in the application mutates a document outside one. ### Phase 1 · A file on disk within the first minute Build: Open, save and save-as through the browser's file interface where available, with an explicit download and reopen path where it is not. Keep the indexed database as a cache keyed by the file handle. Show the time since the document last reached a file. On load, if the cache and the file disagree, present both and let the user choose. Trap: Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. Trap: When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. Done when: Clearing all browser storage loses nothing that had been written to a file, tested by clearing and reopening. The interface displays the time since the last file write and shows a distinct state once it exceeds a configured threshold. A cache newer than the file produces a choice on open and neither copy is discarded, tested by editing then killing the tab. A new document prompts for a file within a configured time and does not let itself be forgotten. Do not build yet: No connectors, undo stack, export or library view yet. ### Phase 2 · Connectors that stay attached Build: Give shapes named ports. Store a connector as source shape and port, target shape and port, plus waypoints held relative to those anchors. Re-route orthogonally when a shape moves, keeping the waypoints that remain geometrically sensible and telling the user when one had to be dropped. Deleting a shape either deletes its connectors or leaves them visibly orphaned, never dangling. Trap: Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. Done when: Moving a shape moves every attached connector endpoint, tested across a multi-connector graph. Deleting a shape leaves no connector referencing a missing identifier, asserted by a document validation run in the suite. A manually bent connector keeps its bends through a small move and reports in the interface when a bend was discarded, tested. Orthogonal routing does not pass through the interiors of other shapes on a generated test layout. Do not build yet: No undo stack, export or library view yet. ### Phase 3 · Undo that is provably complete Build: Route every document mutation through a command object carrying its inverse. Group multi-select operations into one command. Restore selection as part of undo. Add a property test that applies a long random sequence of commands, undoes all of them, and compares the serialised document byte for byte against the starting state. Trap: Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. Done when: A property test applying at least a few hundred random operations and undoing all of them produces a document byte-identical to the start, run in the suite. Every registered command type has an inverse, asserted by a test over the registry. Redo after undo reproduces the state exactly including selection, tested. No code path mutates a document outside a command, checked by review and by a test that the mutation helpers are unexported. Do not build yet: No export, library view or performance work yet. ### Phase 4 · An export you can reopen Build: Export SVG with the source document embedded as metadata so opening that file in the editor gives back the diagram. Offer a font choice in the dialogue: reference the font and state the dependency, or convert text to outlines and state that it becomes uneditable and unsearchable. Export raster at a chosen scale with the resulting pixel dimensions shown before export. Trap: An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. Done when: An exported SVG reopened in the editor produces a document identical to the original, tested by round trip. The export dialogue states the font consequence for each option in words, checked by review. A raster export at a chosen scale has exactly the pixel dimensions shown beforehand, tested. Export completes with the network disabled, tested by running it offline. Do not build yet: No library view, performance budget or crash recovery work yet. ### Phase 5 · Many diagrams, and a canvas that stays usable Build: A library view over a directory the user granted, listing documents with their last write times. A generated large document for measurement, a stated shape count the editor must stay interactive at, and hit testing kept off the render path. Crash recovery from the cache with the divergence choice from Phase 1. A bulk export of a whole folder. Trap: A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. Done when: A generated document at the stated shape count stays interactive while dragging, measured with the frame timings recorded in the test output. The library view lists documents from a granted directory with their last write times and opens one without a full parse of the others. Recovering from the cache after a killed tab offers the same divergence choice as a normal open, tested. Bulk export writes one file per document and reports failures individually rather than aborting. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer cursors and comments, which is a conflict resolution problem and is most of what the paid tier sells. - High-fidelity import from proprietary diagram formats, which is reverse engineering a moving target and never finishes. - A large template and shape library, which is a content business requiring maintenance for every template forever. - Presentation mode and slide export, which is a second application wearing the same interface. ### README must contain - That browser storage is a cache and can be evicted, with the published eviction behaviour named and dated, and what that means for where your diagrams live - How to tell, at a glance, when the current document last reached a file - That connectors are anchored to shapes and ports rather than to coordinates, and what happens to manual bends on a move - The font trade-off in SVG export, in both directions, and that the source document is embedded in the exported file - The shape count the editor is measured against and where that measurement lives in the test suite ===== AGENTS.md ===== # Agent instructions · EdrawMax indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server at all, SVG for the document, with hit testing kept off the render path, Files the user chose, with an indexed database used only as a working cache, A command stack where every mutation carries its inverse, SVG carrying the source document as metadata, plus raster at a chosen scale. 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 - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ===== BUILD_PLAN.md ===== # Build plan · EdrawMax A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A file on disk within the first minute Every document has somewhere real to live and the interface says when it last got there. ### Steps 1. Make the file the document and the cache a cache One of them can be evicted and it is not the file. Files: `src/persist/file.ts`, `src/persist/cache.ts` 2. Show time since the last file write The number that tells you whether your work exists. Files: `src/ui/SaveState.tsx` 3. Detect divergence with a hash and offer both Never resolve somebody else's conflict for them. Files: `src/persist/reconcile.ts`, `tests/reconcile.test.ts` ### Done when - [ ] Clearing browser storage loses nothing saved to a file - [ ] Time since last file write is visible and goes stale - [ ] A cache and file divergence offers both copies - [ ] A new document is prompted for a file quickly ### Watch out - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. ## Phase 2 · Connectors that stay attached Moving a box moves everything joined to it, and manual bends survive. ### Steps 1. Anchor to shape and port, never to coordinates The identifier survives the move; the coordinate does not. Files: `src/model/connectors.ts`, `tests/connectors.test.ts` 2. Keep waypoints relative to the anchors So a small move does not erase somebody's work. Files: `src/routing/waypoints.ts` 3. Validate the document for dangling references Run it in the suite after every mutating operation. Files: `src/model/validate.ts`, `tests/validate.test.ts` ### Done when - [ ] Moving a shape moves its connector endpoints - [ ] No connector references a deleted shape - [ ] Manual bends survive a small move or are reported - [ ] Routing avoids other shapes' interiors ### Watch out - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. ## Phase 3 · Undo that is provably complete Any sequence of operations, fully undone, returns the document exactly as it was. ### Steps 1. Give every mutation a command with an inverse And assert in a test that none is missing one. Files: `src/history/commands.ts`, `tests/inverses.test.ts` 2. Write the property test early, not last It is the cheapest way to find the operation you forgot. Files: `tests/undo-property.test.ts` 3. Restore selection as part of undo Undo that loses your selection makes you check what happened. Files: `src/history/stack.ts` ### Done when - [ ] A long random sequence undoes to a byte-identical document - [ ] Every command type has an inverse - [ ] Redo reproduces state and selection - [ ] Nothing mutates a document outside a command ### Watch out - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. ## Phase 4 · An export you can reopen The SVG that goes to a colleague carries the document that made it. ### Steps 1. Embed the source document in the SVG The picture and the document should be one file. Files: `src/export/svg.ts`, `tests/export-roundtrip.test.ts` 2. Put the font trade-off in the dialogue Both consequences, in words, at the moment of choosing. Files: `src/ui/ExportDialog.tsx` 3. Show the pixel dimensions before exporting Nobody wants to guess what scale four means. Files: `src/export/raster.ts` ### Done when - [ ] An exported SVG reopens to an identical document - [ ] The dialogue states the font consequence - [ ] Raster dimensions match what was shown - [ ] Export works offline ### Watch out - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ## Not in this build - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. ## After v1, if you want it - Automatic layout for a graph you paste in, which is a solved algorithm and a large quality jump - A text format for the document that reads well in a diff, so diagrams can live beside code - Shape libraries loaded from a file, once the document format has stopped changing ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The indexed database used for crash recovery only. Evictable by design. CACHE_DB_NAME=diagram-cache # Required. How long a new document may go without a file before the editor insists. SAVE_PROMPT_SECONDS=120 # Required. After this long without a file write, the indicator changes state visibly. STALE_SAVE_WARN_MINUTES=10 # Required. The size the performance test generates and the README promises. TARGET_SHAPE_COUNT=500 # Required. Default raster scale. The dialogue shows resulting pixel dimensions. EXPORT_SCALE_DEFAULT=2
You are building a production product version of EdrawMax. 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 ===== # EdrawMax · product brief ## Problem The visible general diagramming loop is buildable, but a credible replacement needs more than the first screen. EdrawMax earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A diagram editor that is never the only copy of your work, whose connectors stay attached, whose undo is checked by a property test, and whose exported picture carries the document that made it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - modern browser - no account required ## Explicit non-goals for v1 - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments ## Success criteria - Clearing browser storage is confirmed to lose nothing saved to a file - No connector is confirmed to reference a deleted shape - A random command sequence is confirmed to undo byte-identically - An exported SVG is confirmed to reopen identically - The stated shape count is confirmed interactive under measurement ===== BRIEF.md ===== # Build brief · EdrawMax 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 diagramming editor whose documents live in files you chose, with connectors that stay attached and an export you can reopen to replace EdrawMax. 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. ### Rule zero Browser storage is a cache. It is not a place to leave a diagram, and the reason is published. When WebKit announced full third-party cookie blocking on 24 March 2020 it also described a seven-day cap on script-writable storage: after seven days of Safari use without user interaction on the site, indexed databases, local storage, session storage, media keys and service worker registrations and their caches are deleted. Interaction resets the counter, and a web application added to the home screen is counted separately. Check whether that behaviour still stands in the browsers you care about before you decide either way, because the direction of travel across browsers is towards eviction rather than away from it. An editor whose only copy of your diagram is an indexed database is an editor that loses the work of somebody who took a fortnight off. So: the browser holds a working cache, and every document also lives in a real file the user chose, written through the file system interface where the browser has one and through an explicit download and reopen where it does not. The interface states, where you can see it without looking, when this document last reached a file, and that indicator goes stale loudly. The corollary: the export is the format. If a diagram cannot be reopened from the thing you exported, you made a picture rather than a document, and you should say so in the dialogue. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server at all · a diagram editor that needs a backend to draw a rectangle has chosen wrong - Canvas: SVG for the document, with hit testing kept off the render path · SVG gives you text, selection and export in the same representation - Persistence: Files the user chose, with an indexed database used only as a working cache · browser storage is evictable and the published eviction rules are why - History: A command stack where every mutation carries its inverse · undo built by snapshotting is slow and undo built by hand is incomplete - Export: SVG carrying the source document as metadata, plus raster at a chosen scale · an export that cannot be reopened is a screenshot ### Data model (create this before Phase 1) - the document file: format_version, pages, shapes, connectors, styles · one JSON object, readable without the editor - pages(id, name, ordinal, width, height, grid) · a document is pages, and a single page is a document with one - shapes(id, page_id, kind, x, y, w, h, rotation, style_ref, text, ports_json) · ports are named points, because connectors attach to names rather than coordinates - connectors(id, page_id, from_shape, from_port, to_shape, to_port, waypoints_json, routing, style_ref) · never two absolute points - styles(id, name, declarations_json) · named, so changing the look of every process box is one edit - file_state(handle_ref, last_written_at, last_written_hash, dirty) · surfaced in the interface rather than living in a variable - commands: an in-memory stack of do and undo pairs; nothing in the application mutates a document outside one. ### Phase 1 · A file on disk within the first minute Build: Open, save and save-as through the browser's file interface where available, with an explicit download and reopen path where it is not. Keep the indexed database as a cache keyed by the file handle. Show the time since the document last reached a file. On load, if the cache and the file disagree, present both and let the user choose. Trap: Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. Trap: When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. Done when: Clearing all browser storage loses nothing that had been written to a file, tested by clearing and reopening. The interface displays the time since the last file write and shows a distinct state once it exceeds a configured threshold. A cache newer than the file produces a choice on open and neither copy is discarded, tested by editing then killing the tab. A new document prompts for a file within a configured time and does not let itself be forgotten. Do not build yet: No connectors, undo stack, export or library view yet. ### Phase 2 · Connectors that stay attached Build: Give shapes named ports. Store a connector as source shape and port, target shape and port, plus waypoints held relative to those anchors. Re-route orthogonally when a shape moves, keeping the waypoints that remain geometrically sensible and telling the user when one had to be dropped. Deleting a shape either deletes its connectors or leaves them visibly orphaned, never dangling. Trap: Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. Done when: Moving a shape moves every attached connector endpoint, tested across a multi-connector graph. Deleting a shape leaves no connector referencing a missing identifier, asserted by a document validation run in the suite. A manually bent connector keeps its bends through a small move and reports in the interface when a bend was discarded, tested. Orthogonal routing does not pass through the interiors of other shapes on a generated test layout. Do not build yet: No undo stack, export or library view yet. ### Phase 3 · Undo that is provably complete Build: Route every document mutation through a command object carrying its inverse. Group multi-select operations into one command. Restore selection as part of undo. Add a property test that applies a long random sequence of commands, undoes all of them, and compares the serialised document byte for byte against the starting state. Trap: Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. Done when: A property test applying at least a few hundred random operations and undoing all of them produces a document byte-identical to the start, run in the suite. Every registered command type has an inverse, asserted by a test over the registry. Redo after undo reproduces the state exactly including selection, tested. No code path mutates a document outside a command, checked by review and by a test that the mutation helpers are unexported. Do not build yet: No export, library view or performance work yet. ### Phase 4 · An export you can reopen Build: Export SVG with the source document embedded as metadata so opening that file in the editor gives back the diagram. Offer a font choice in the dialogue: reference the font and state the dependency, or convert text to outlines and state that it becomes uneditable and unsearchable. Export raster at a chosen scale with the resulting pixel dimensions shown before export. Trap: An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. Done when: An exported SVG reopened in the editor produces a document identical to the original, tested by round trip. The export dialogue states the font consequence for each option in words, checked by review. A raster export at a chosen scale has exactly the pixel dimensions shown beforehand, tested. Export completes with the network disabled, tested by running it offline. Do not build yet: No library view, performance budget or crash recovery work yet. ### Phase 5 · Many diagrams, and a canvas that stays usable Build: A library view over a directory the user granted, listing documents with their last write times. A generated large document for measurement, a stated shape count the editor must stay interactive at, and hit testing kept off the render path. Crash recovery from the cache with the divergence choice from Phase 1. A bulk export of a whole folder. Trap: A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. Done when: A generated document at the stated shape count stays interactive while dragging, measured with the frame timings recorded in the test output. The library view lists documents from a granted directory with their last write times and opens one without a full parse of the others. Recovering from the cache after a killed tab offers the same divergence choice as a normal open, tested. Bulk export writes one file per document and reports failures individually rather than aborting. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer cursors and comments, which is a conflict resolution problem and is most of what the paid tier sells. - High-fidelity import from proprietary diagram formats, which is reverse engineering a moving target and never finishes. - A large template and shape library, which is a content business requiring maintenance for every template forever. - Presentation mode and slide export, which is a second application wearing the same interface. ### README must contain - That browser storage is a cache and can be evicted, with the published eviction behaviour named and dated, and what that means for where your diagrams live - How to tell, at a glance, when the current document last reached a file - That connectors are anchored to shapes and ports rather than to coordinates, and what happens to manual bends on a move - The font trade-off in SVG export, in both directions, and that the source document is embedded in the exported file - The shape count the editor is measured against and where that measurement lives in the test suite ===== ARCHITECTURE.md ===== # Architecture · EdrawMax ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server at all | a diagram editor that needs a backend to draw a rectangle has chosen wrong | | Canvas | SVG for the document, with hit testing kept off the render path | SVG gives you text, selection and export in the same representation | | Persistence | Files the user chose, with an indexed database used only as a working cache | browser storage is evictable and the published eviction rules are why | | History | A command stack where every mutation carries its inverse | undo built by snapshotting is slow and undo built by hand is incomplete | | Export | SVG carrying the source document as metadata, plus raster at a chosen scale | an export that cannot be reopened is a screenshot | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Persistence | File handles, the cache and divergence handling. | Any storage, provided the file is the document and the cache is a cache. | | Model | Shapes, ports, connectors and document validation. | Any geometry, provided connectors anchor to identifiers. | | History | Commands, inverses and the stack. | Any implementation, provided the property test still passes. | | Export | SVG with embedded source, raster and the font choice. | Any renderer, provided an exported SVG reopens. | | Library | Directory listing, bulk export and recovery. | Any view, provided opening one document does not parse the rest. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `CACHE_DB_NAME` · required · The indexed database used for crash recovery only. Evictable by design. - `SAVE_PROMPT_SECONDS` · required · How long a new document may go without a file before the editor insists. - `STALE_SAVE_WARN_MINUTES` · required · After this long without a file write, the indicator changes state visibly. - `TARGET_SHAPE_COUNT` · required · The size the performance test generates and the README promises. - `EXPORT_SCALE_DEFAULT` · required · Default raster scale. The dialogue shows resulting pixel dimensions. ## 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 · EdrawMax product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, no server at all, SVG for the document, with hit testing kept off the render path, Files the user chose, with an indexed database used only as a working cache, A command stack where every mutation carries its inverse, SVG carrying the source document as metadata, plus raster at a chosen scale. - 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 - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. - A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. ===== MILESTONES.md ===== # Delivery milestones · EdrawMax Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A file on disk within the first minute Every document has somewhere real to live and the interface says when it last got there. ### Steps 1. Make the file the document and the cache a cache One of them can be evicted and it is not the file. Files: `src/persist/file.ts`, `src/persist/cache.ts` 2. Show time since the last file write The number that tells you whether your work exists. Files: `src/ui/SaveState.tsx` 3. Detect divergence with a hash and offer both Never resolve somebody else's conflict for them. Files: `src/persist/reconcile.ts`, `tests/reconcile.test.ts` ### Done when - [ ] Clearing browser storage loses nothing saved to a file - [ ] Time since last file write is visible and goes stale - [ ] A cache and file divergence offers both copies - [ ] A new document is prompted for a file quickly ### Watch out - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. ## M2 · Connectors that stay attached Moving a box moves everything joined to it, and manual bends survive. ### Steps 1. Anchor to shape and port, never to coordinates The identifier survives the move; the coordinate does not. Files: `src/model/connectors.ts`, `tests/connectors.test.ts` 2. Keep waypoints relative to the anchors So a small move does not erase somebody's work. Files: `src/routing/waypoints.ts` 3. Validate the document for dangling references Run it in the suite after every mutating operation. Files: `src/model/validate.ts`, `tests/validate.test.ts` ### Done when - [ ] Moving a shape moves its connector endpoints - [ ] No connector references a deleted shape - [ ] Manual bends survive a small move or are reported - [ ] Routing avoids other shapes' interiors ### Watch out - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. ## M3 · Undo that is provably complete Any sequence of operations, fully undone, returns the document exactly as it was. ### Steps 1. Give every mutation a command with an inverse And assert in a test that none is missing one. Files: `src/history/commands.ts`, `tests/inverses.test.ts` 2. Write the property test early, not last It is the cheapest way to find the operation you forgot. Files: `tests/undo-property.test.ts` 3. Restore selection as part of undo Undo that loses your selection makes you check what happened. Files: `src/history/stack.ts` ### Done when - [ ] A long random sequence undoes to a byte-identical document - [ ] Every command type has an inverse - [ ] Redo reproduces state and selection - [ ] Nothing mutates a document outside a command ### Watch out - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. ## M4 · An export you can reopen The SVG that goes to a colleague carries the document that made it. ### Steps 1. Embed the source document in the SVG The picture and the document should be one file. Files: `src/export/svg.ts`, `tests/export-roundtrip.test.ts` 2. Put the font trade-off in the dialogue Both consequences, in words, at the moment of choosing. Files: `src/ui/ExportDialog.tsx` 3. Show the pixel dimensions before exporting Nobody wants to guess what scale four means. Files: `src/export/raster.ts` ### Done when - [ ] An exported SVG reopens to an identical document - [ ] The dialogue states the font consequence - [ ] Raster dimensions match what was shown - [ ] Export works offline ### Watch out - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ## M5 · Many diagrams, and a canvas that stays usable (production only) A folder of documents opens quickly and a large diagram still responds. ### Steps 1. Generate a large document and measure it The claimed limit should be a test, not an impression. Files: `tests/perf/large-doc.test.ts`, `scripts/generate-doc.ts` 2. Index shapes spatially for hit testing Dragging should not walk the document. Files: `src/canvas/hittest.ts` 3. Make bulk export report per document One bad file should not abort forty good ones. Files: `src/export/bulk.ts` ### Done when - [ ] The stated shape count stays interactive under measurement - [ ] The library lists a directory without parsing everything - [ ] Crash recovery offers the divergence choice - [ ] Bulk export reports failures individually ### Watch out - A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. ===== OPERATIONS.md ===== # Operations · EdrawMax ## Backup The files, wherever the user chose to put them, which is the point of the whole persistence design: backup becomes somebody else's solved problem rather than a feature you had to write. The cache is never backed up. ## Restore Open a file and confirm it renders with connectors attached and labels in place. If the file opens and the diagram is right, there is nothing else to restore, which is the test of whether the format decision was correct. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor the checks live in the suite: the undo property test, the document validation run after every mutating operation, the export round trip, and the frame timing at the stated shape count. Watch the last one after any rendering change. ## Incident checklist The incident is a lost diagram, and almost always it is a diagram that only ever existed in browser storage. The save-state indicator and the insistence on a file early are the controls that prevent it, and the cache is what recovers the last few minutes when a tab dies. 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 - [ ] Clearing browser storage is confirmed to lose nothing saved to a file - [ ] No connector is confirmed to reference a deleted shape - [ ] A random command sequence is confirmed to undo byte-identically - [ ] An exported SVG is confirmed to reopen identically - [ ] The stated shape count is confirmed interactive under measurement ## Launch constraint Do not market omitted EdrawMax 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 indexed database used for crash recovery only. Evictable by design. CACHE_DB_NAME=diagram-cache # Required. How long a new document may go without a file before the editor insists. SAVE_PROMPT_SECONDS=120 # Required. After this long without a file write, the indicator changes state visibly. STALE_SAVE_WARN_MINUTES=10 # Required. The size the performance test generates and the README promises. TARGET_SHAPE_COUNT=500 # Required. Default raster scale. The dialogue shows resulting pixel dimensions. EXPORT_SCALE_DEFAULT=2
# EdrawMax · indie build A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. 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 | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server at all | a diagram editor that needs a backend to draw a rectangle has chosen wrong | | Canvas | SVG for the document, with hit testing kept off the render path | SVG gives you text, selection and export in the same representation | | Persistence | Files the user chose, with an indexed database used only as a working cache | browser storage is evictable and the published eviction rules are why | | History | A command stack where every mutation carries its inverse | undo built by snapshotting is slow and undo built by hand is incomplete | | Export | SVG carrying the source document as metadata, plus raster at a chosen scale | an export that cannot be reopened is a screenshot | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the development server, the build and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Two browsers, one with a file system interface and one without** · free - Why: The persistence design exists because these behave differently, and testing in only one of them guarantees you ship the fallback untested. - Get it: Any two current browsers from different engine families. - Verify: Save and reopen work in both, by the intended path in each - [ ] **What the browser cache is allowed to be the only copy of** · free - Why: This is the decision the whole build hangs on, and making it implicitly means making it wrong. - Get it: Write down that the cache covers unsaved changes since the last file write and nothing else. - Verify: The statement appears in the README and a test clears storage and loses nothing saved - [ ] **A real diagram you already need** · free - Why: Connector routing and text sizing only misbehave on real content, and a demo of four boxes will never show you the problem. - Get it: Rebuild an architecture or process diagram you actually use. - Verify: It renders, exports and reopens with labels intact ## 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: - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments If one of those is essential to you, that is the reason to keep paying for EdrawMax, and the README should say so rather than pretend.
# Build brief · EdrawMax 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 diagramming editor whose documents live in files you chose, with connectors that stay attached and an export you can reopen to replace EdrawMax. 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. ### Rule zero Browser storage is a cache. It is not a place to leave a diagram, and the reason is published. When WebKit announced full third-party cookie blocking on 24 March 2020 it also described a seven-day cap on script-writable storage: after seven days of Safari use without user interaction on the site, indexed databases, local storage, session storage, media keys and service worker registrations and their caches are deleted. Interaction resets the counter, and a web application added to the home screen is counted separately. Check whether that behaviour still stands in the browsers you care about before you decide either way, because the direction of travel across browsers is towards eviction rather than away from it. An editor whose only copy of your diagram is an indexed database is an editor that loses the work of somebody who took a fortnight off. So: the browser holds a working cache, and every document also lives in a real file the user chose, written through the file system interface where the browser has one and through an explicit download and reopen where it does not. The interface states, where you can see it without looking, when this document last reached a file, and that indicator goes stale loudly. The corollary: the export is the format. If a diagram cannot be reopened from the thing you exported, you made a picture rather than a document, and you should say so in the dialogue. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server at all · a diagram editor that needs a backend to draw a rectangle has chosen wrong - Canvas: SVG for the document, with hit testing kept off the render path · SVG gives you text, selection and export in the same representation - Persistence: Files the user chose, with an indexed database used only as a working cache · browser storage is evictable and the published eviction rules are why - History: A command stack where every mutation carries its inverse · undo built by snapshotting is slow and undo built by hand is incomplete - Export: SVG carrying the source document as metadata, plus raster at a chosen scale · an export that cannot be reopened is a screenshot ### Data model (create this before Phase 1) - the document file: format_version, pages, shapes, connectors, styles · one JSON object, readable without the editor - pages(id, name, ordinal, width, height, grid) · a document is pages, and a single page is a document with one - shapes(id, page_id, kind, x, y, w, h, rotation, style_ref, text, ports_json) · ports are named points, because connectors attach to names rather than coordinates - connectors(id, page_id, from_shape, from_port, to_shape, to_port, waypoints_json, routing, style_ref) · never two absolute points - styles(id, name, declarations_json) · named, so changing the look of every process box is one edit - file_state(handle_ref, last_written_at, last_written_hash, dirty) · surfaced in the interface rather than living in a variable - commands: an in-memory stack of do and undo pairs; nothing in the application mutates a document outside one. ### Phase 1 · A file on disk within the first minute Build: Open, save and save-as through the browser's file interface where available, with an explicit download and reopen path where it is not. Keep the indexed database as a cache keyed by the file handle. Show the time since the document last reached a file. On load, if the cache and the file disagree, present both and let the user choose. Trap: Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. Trap: When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. Done when: Clearing all browser storage loses nothing that had been written to a file, tested by clearing and reopening. The interface displays the time since the last file write and shows a distinct state once it exceeds a configured threshold. A cache newer than the file produces a choice on open and neither copy is discarded, tested by editing then killing the tab. A new document prompts for a file within a configured time and does not let itself be forgotten. Do not build yet: No connectors, undo stack, export or library view yet. ### Phase 2 · Connectors that stay attached Build: Give shapes named ports. Store a connector as source shape and port, target shape and port, plus waypoints held relative to those anchors. Re-route orthogonally when a shape moves, keeping the waypoints that remain geometrically sensible and telling the user when one had to be dropped. Deleting a shape either deletes its connectors or leaves them visibly orphaned, never dangling. Trap: Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. Done when: Moving a shape moves every attached connector endpoint, tested across a multi-connector graph. Deleting a shape leaves no connector referencing a missing identifier, asserted by a document validation run in the suite. A manually bent connector keeps its bends through a small move and reports in the interface when a bend was discarded, tested. Orthogonal routing does not pass through the interiors of other shapes on a generated test layout. Do not build yet: No undo stack, export or library view yet. ### Phase 3 · Undo that is provably complete Build: Route every document mutation through a command object carrying its inverse. Group multi-select operations into one command. Restore selection as part of undo. Add a property test that applies a long random sequence of commands, undoes all of them, and compares the serialised document byte for byte against the starting state. Trap: Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. Done when: A property test applying at least a few hundred random operations and undoing all of them produces a document byte-identical to the start, run in the suite. Every registered command type has an inverse, asserted by a test over the registry. Redo after undo reproduces the state exactly including selection, tested. No code path mutates a document outside a command, checked by review and by a test that the mutation helpers are unexported. Do not build yet: No export, library view or performance work yet. ### Phase 4 · An export you can reopen Build: Export SVG with the source document embedded as metadata so opening that file in the editor gives back the diagram. Offer a font choice in the dialogue: reference the font and state the dependency, or convert text to outlines and state that it becomes uneditable and unsearchable. Export raster at a chosen scale with the resulting pixel dimensions shown before export. Trap: An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. Done when: An exported SVG reopened in the editor produces a document identical to the original, tested by round trip. The export dialogue states the font consequence for each option in words, checked by review. A raster export at a chosen scale has exactly the pixel dimensions shown beforehand, tested. Export completes with the network disabled, tested by running it offline. Do not build yet: No library view, performance budget or crash recovery work yet. ### Phase 5 · Many diagrams, and a canvas that stays usable Build: A library view over a directory the user granted, listing documents with their last write times. A generated large document for measurement, a stated shape count the editor must stay interactive at, and hit testing kept off the render path. Crash recovery from the cache with the divergence choice from Phase 1. A bulk export of a whole folder. Trap: A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. Done when: A generated document at the stated shape count stays interactive while dragging, measured with the frame timings recorded in the test output. The library view lists documents from a granted directory with their last write times and opens one without a full parse of the others. Recovering from the cache after a killed tab offers the same divergence choice as a normal open, tested. Bulk export writes one file per document and reports failures individually rather than aborting. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer cursors and comments, which is a conflict resolution problem and is most of what the paid tier sells. - High-fidelity import from proprietary diagram formats, which is reverse engineering a moving target and never finishes. - A large template and shape library, which is a content business requiring maintenance for every template forever. - Presentation mode and slide export, which is a second application wearing the same interface. ### README must contain - That browser storage is a cache and can be evicted, with the published eviction behaviour named and dated, and what that means for where your diagrams live - How to tell, at a glance, when the current document last reached a file - That connectors are anchored to shapes and ports rather than to coordinates, and what happens to manual bends on a move - The font trade-off in SVG export, in both directions, and that the source document is embedded in the exported file - The shape count the editor is measured against and where that measurement lives in the test suite
# Agent instructions · EdrawMax indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server at all, SVG for the document, with hit testing kept off the render path, Files the user chose, with an indexed database used only as a working cache, A command stack where every mutation carries its inverse, SVG carrying the source document as metadata, plus raster at a chosen scale. 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 - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen.
# Build plan · EdrawMax A browser diagram editor whose hard problems are never being the only copy of your own document, connectors that stay attached to shapes through every move, and an undo stack that is provably complete rather than mostly complete. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A file on disk within the first minute Every document has somewhere real to live and the interface says when it last got there. ### Steps 1. Make the file the document and the cache a cache One of them can be evicted and it is not the file. Files: `src/persist/file.ts`, `src/persist/cache.ts` 2. Show time since the last file write The number that tells you whether your work exists. Files: `src/ui/SaveState.tsx` 3. Detect divergence with a hash and offer both Never resolve somebody else's conflict for them. Files: `src/persist/reconcile.ts`, `tests/reconcile.test.ts` ### Done when - [ ] Clearing browser storage loses nothing saved to a file - [ ] Time since last file write is visible and goes stale - [ ] A cache and file divergence offers both copies - [ ] A new document is prompted for a file quickly ### Watch out - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. ## Phase 2 · Connectors that stay attached Moving a box moves everything joined to it, and manual bends survive. ### Steps 1. Anchor to shape and port, never to coordinates The identifier survives the move; the coordinate does not. Files: `src/model/connectors.ts`, `tests/connectors.test.ts` 2. Keep waypoints relative to the anchors So a small move does not erase somebody's work. Files: `src/routing/waypoints.ts` 3. Validate the document for dangling references Run it in the suite after every mutating operation. Files: `src/model/validate.ts`, `tests/validate.test.ts` ### Done when - [ ] Moving a shape moves its connector endpoints - [ ] No connector references a deleted shape - [ ] Manual bends survive a small move or are reported - [ ] Routing avoids other shapes' interiors ### Watch out - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. ## Phase 3 · Undo that is provably complete Any sequence of operations, fully undone, returns the document exactly as it was. ### Steps 1. Give every mutation a command with an inverse And assert in a test that none is missing one. Files: `src/history/commands.ts`, `tests/inverses.test.ts` 2. Write the property test early, not last It is the cheapest way to find the operation you forgot. Files: `tests/undo-property.test.ts` 3. Restore selection as part of undo Undo that loses your selection makes you check what happened. Files: `src/history/stack.ts` ### Done when - [ ] A long random sequence undoes to a byte-identical document - [ ] Every command type has an inverse - [ ] Redo reproduces state and selection - [ ] Nothing mutates a document outside a command ### Watch out - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. ## Phase 4 · An export you can reopen The SVG that goes to a colleague carries the document that made it. ### Steps 1. Embed the source document in the SVG The picture and the document should be one file. Files: `src/export/svg.ts`, `tests/export-roundtrip.test.ts` 2. Put the font trade-off in the dialogue Both consequences, in words, at the moment of choosing. Files: `src/ui/ExportDialog.tsx` 3. Show the pixel dimensions before exporting Nobody wants to guess what scale four means. Files: `src/export/raster.ts` ### Done when - [ ] An exported SVG reopens to an identical document - [ ] The dialogue states the font consequence - [ ] Raster dimensions match what was shown - [ ] Export works offline ### Watch out - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ## Not in this build - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. ## After v1, if you want it - Automatic layout for a graph you paste in, which is a solved algorithm and a large quality jump - A text format for the document that reads well in a diff, so diagrams can live beside code - Shape libraries loaded from a file, once the document format has stopped changing
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The indexed database used for crash recovery only. Evictable by design. CACHE_DB_NAME=diagram-cache # Required. How long a new document may go without a file before the editor insists. SAVE_PROMPT_SECONDS=120 # Required. After this long without a file write, the indicator changes state visibly. STALE_SAVE_WARN_MINUTES=10 # Required. The size the performance test generates and the README promises. TARGET_SHAPE_COUNT=500 # Required. Default raster scale. The dialogue shows resulting pixel dimensions. EXPORT_SCALE_DEFAULT=2
# EdrawMax · product brief ## Problem The visible general diagramming loop is buildable, but a credible replacement needs more than the first screen. EdrawMax earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A diagram editor that is never the only copy of your work, whose connectors stay attached, whose undo is checked by a property test, and whose exported picture carries the document that made it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - modern browser - no account required ## Explicit non-goals for v1 - Multiplayer cursors and comments. - Import from proprietary diagram formats. - A large template and shape library. - Presentation mode and slide export. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments ## Success criteria - Clearing browser storage is confirmed to lose nothing saved to a file - No connector is confirmed to reference a deleted shape - A random command sequence is confirmed to undo byte-identically - An exported SVG is confirmed to reopen identically - The stated shape count is confirmed interactive under measurement
# Build brief · EdrawMax 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 diagramming editor whose documents live in files you chose, with connectors that stay attached and an export you can reopen to replace EdrawMax. 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. ### Rule zero Browser storage is a cache. It is not a place to leave a diagram, and the reason is published. When WebKit announced full third-party cookie blocking on 24 March 2020 it also described a seven-day cap on script-writable storage: after seven days of Safari use without user interaction on the site, indexed databases, local storage, session storage, media keys and service worker registrations and their caches are deleted. Interaction resets the counter, and a web application added to the home screen is counted separately. Check whether that behaviour still stands in the browsers you care about before you decide either way, because the direction of travel across browsers is towards eviction rather than away from it. An editor whose only copy of your diagram is an indexed database is an editor that loses the work of somebody who took a fortnight off. So: the browser holds a working cache, and every document also lives in a real file the user chose, written through the file system interface where the browser has one and through an explicit download and reopen where it does not. The interface states, where you can see it without looking, when this document last reached a file, and that indicator goes stale loudly. The corollary: the export is the format. If a diagram cannot be reopened from the thing you exported, you made a picture rather than a document, and you should say so in the dialogue. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server at all · a diagram editor that needs a backend to draw a rectangle has chosen wrong - Canvas: SVG for the document, with hit testing kept off the render path · SVG gives you text, selection and export in the same representation - Persistence: Files the user chose, with an indexed database used only as a working cache · browser storage is evictable and the published eviction rules are why - History: A command stack where every mutation carries its inverse · undo built by snapshotting is slow and undo built by hand is incomplete - Export: SVG carrying the source document as metadata, plus raster at a chosen scale · an export that cannot be reopened is a screenshot ### Data model (create this before Phase 1) - the document file: format_version, pages, shapes, connectors, styles · one JSON object, readable without the editor - pages(id, name, ordinal, width, height, grid) · a document is pages, and a single page is a document with one - shapes(id, page_id, kind, x, y, w, h, rotation, style_ref, text, ports_json) · ports are named points, because connectors attach to names rather than coordinates - connectors(id, page_id, from_shape, from_port, to_shape, to_port, waypoints_json, routing, style_ref) · never two absolute points - styles(id, name, declarations_json) · named, so changing the look of every process box is one edit - file_state(handle_ref, last_written_at, last_written_hash, dirty) · surfaced in the interface rather than living in a variable - commands: an in-memory stack of do and undo pairs; nothing in the application mutates a document outside one. ### Phase 1 · A file on disk within the first minute Build: Open, save and save-as through the browser's file interface where available, with an explicit download and reopen path where it is not. Keep the indexed database as a cache keyed by the file handle. Show the time since the document last reached a file. On load, if the cache and the file disagree, present both and let the user choose. Trap: Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. Trap: When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. Done when: Clearing all browser storage loses nothing that had been written to a file, tested by clearing and reopening. The interface displays the time since the last file write and shows a distinct state once it exceeds a configured threshold. A cache newer than the file produces a choice on open and neither copy is discarded, tested by editing then killing the tab. A new document prompts for a file within a configured time and does not let itself be forgotten. Do not build yet: No connectors, undo stack, export or library view yet. ### Phase 2 · Connectors that stay attached Build: Give shapes named ports. Store a connector as source shape and port, target shape and port, plus waypoints held relative to those anchors. Re-route orthogonally when a shape moves, keeping the waypoints that remain geometrically sensible and telling the user when one had to be dropped. Deleting a shape either deletes its connectors or leaves them visibly orphaned, never dangling. Trap: Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. Done when: Moving a shape moves every attached connector endpoint, tested across a multi-connector graph. Deleting a shape leaves no connector referencing a missing identifier, asserted by a document validation run in the suite. A manually bent connector keeps its bends through a small move and reports in the interface when a bend was discarded, tested. Orthogonal routing does not pass through the interiors of other shapes on a generated test layout. Do not build yet: No undo stack, export or library view yet. ### Phase 3 · Undo that is provably complete Build: Route every document mutation through a command object carrying its inverse. Group multi-select operations into one command. Restore selection as part of undo. Add a property test that applies a long random sequence of commands, undoes all of them, and compares the serialised document byte for byte against the starting state. Trap: Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. Done when: A property test applying at least a few hundred random operations and undoing all of them produces a document byte-identical to the start, run in the suite. Every registered command type has an inverse, asserted by a test over the registry. Redo after undo reproduces the state exactly including selection, tested. No code path mutates a document outside a command, checked by review and by a test that the mutation helpers are unexported. Do not build yet: No export, library view or performance work yet. ### Phase 4 · An export you can reopen Build: Export SVG with the source document embedded as metadata so opening that file in the editor gives back the diagram. Offer a font choice in the dialogue: reference the font and state the dependency, or convert text to outlines and state that it becomes uneditable and unsearchable. Export raster at a chosen scale with the resulting pixel dimensions shown before export. Trap: An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. Done when: An exported SVG reopened in the editor produces a document identical to the original, tested by round trip. The export dialogue states the font consequence for each option in words, checked by review. A raster export at a chosen scale has exactly the pixel dimensions shown beforehand, tested. Export completes with the network disabled, tested by running it offline. Do not build yet: No library view, performance budget or crash recovery work yet. ### Phase 5 · Many diagrams, and a canvas that stays usable Build: A library view over a directory the user granted, listing documents with their last write times. A generated large document for measurement, a stated shape count the editor must stay interactive at, and hit testing kept off the render path. Crash recovery from the cache with the divergence choice from Phase 1. A bulk export of a whole folder. Trap: A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope. Done when: A generated document at the stated shape count stays interactive while dragging, measured with the frame timings recorded in the test output. The library view lists documents from a granted directory with their last write times and opens one without a full parse of the others. Recovering from the cache after a killed tab offers the same divergence choice as a normal open, tested. Bulk export writes one file per document and reports failures individually rather than aborting. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer cursors and comments, which is a conflict resolution problem and is most of what the paid tier sells. - High-fidelity import from proprietary diagram formats, which is reverse engineering a moving target and never finishes. - A large template and shape library, which is a content business requiring maintenance for every template forever. - Presentation mode and slide export, which is a second application wearing the same interface. ### README must contain - That browser storage is a cache and can be evicted, with the published eviction behaviour named and dated, and what that means for where your diagrams live - How to tell, at a glance, when the current document last reached a file - That connectors are anchored to shapes and ports rather than to coordinates, and what happens to manual bends on a move - The font trade-off in SVG export, in both directions, and that the source document is embedded in the exported file - The shape count the editor is measured against and where that measurement lives in the test suite
# Architecture · EdrawMax ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server at all | a diagram editor that needs a backend to draw a rectangle has chosen wrong | | Canvas | SVG for the document, with hit testing kept off the render path | SVG gives you text, selection and export in the same representation | | Persistence | Files the user chose, with an indexed database used only as a working cache | browser storage is evictable and the published eviction rules are why | | History | A command stack where every mutation carries its inverse | undo built by snapshotting is slow and undo built by hand is incomplete | | Export | SVG carrying the source document as metadata, plus raster at a chosen scale | an export that cannot be reopened is a screenshot | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Persistence | File handles, the cache and divergence handling. | Any storage, provided the file is the document and the cache is a cache. | | Model | Shapes, ports, connectors and document validation. | Any geometry, provided connectors anchor to identifiers. | | History | Commands, inverses and the stack. | Any implementation, provided the property test still passes. | | Export | SVG with embedded source, raster and the font choice. | Any renderer, provided an exported SVG reopens. | | Library | Directory listing, bulk export and recovery. | Any view, provided opening one document does not parse the rest. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `CACHE_DB_NAME` · required · The indexed database used for crash recovery only. Evictable by design. - `SAVE_PROMPT_SECONDS` · required · How long a new document may go without a file before the editor insists. - `STALE_SAVE_WARN_MINUTES` · required · After this long without a file write, the indicator changes state visibly. - `TARGET_SHAPE_COUNT` · required · The size the performance test generates and the README promises. - `EXPORT_SCALE_DEFAULT` · required · Default raster scale. The dialogue shows resulting pixel dimensions. ## 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 · EdrawMax product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, no server at all, SVG for the document, with hit testing kept off the render path, Files the user chose, with an indexed database used only as a working cache, A command stack where every mutation carries its inverse, SVG carrying the source document as metadata, plus raster at a chosen scale. - 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 - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. - A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope.
# Delivery milestones · EdrawMax Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A file on disk within the first minute Every document has somewhere real to live and the interface says when it last got there. ### Steps 1. Make the file the document and the cache a cache One of them can be evicted and it is not the file. Files: `src/persist/file.ts`, `src/persist/cache.ts` 2. Show time since the last file write The number that tells you whether your work exists. Files: `src/ui/SaveState.tsx` 3. Detect divergence with a hash and offer both Never resolve somebody else's conflict for them. Files: `src/persist/reconcile.ts`, `tests/reconcile.test.ts` ### Done when - [ ] Clearing browser storage loses nothing saved to a file - [ ] Time since last file write is visible and goes stale - [ ] A cache and file divergence offers both copies - [ ] A new document is prompted for a file quickly ### Watch out - Treating the indexed database as the document store gives you a fast first version and a support request in six weeks from somebody whose month-old diagram is gone, because script-writable storage is evictable under published rules and the user did nothing wrong. Write to a file the user chose, keep the cache for crash recovery only, and make the last-written-to-file time something on screen rather than something in a console log. - When the cache and the file disagree after a crash, silently preferring one of them loses the other, and whichever you pick will be the wrong one for somebody. Store a hash of what you last wrote, detect the divergence on open, and offer both copies with their times rather than resolving it for the user. ## M2 · Connectors that stay attached Moving a box moves everything joined to it, and manual bends survive. ### Steps 1. Anchor to shape and port, never to coordinates The identifier survives the move; the coordinate does not. Files: `src/model/connectors.ts`, `tests/connectors.test.ts` 2. Keep waypoints relative to the anchors So a small move does not erase somebody's work. Files: `src/routing/waypoints.ts` 3. Validate the document for dangling references Run it in the suite after every mutating operation. Files: `src/model/validate.ts`, `tests/validate.test.ts` ### Done when - [ ] Moving a shape moves its connector endpoints - [ ] No connector references a deleted shape - [ ] Manual bends survive a small move or are reported - [ ] Routing avoids other shapes' interiors ### Watch out - Storing a connector as two absolute points takes five minutes and detaches the first time anybody moves a box, which is the single thing that makes a diagram tool feel broken; re-routing from scratch on every move instead throws away the bends somebody spent ten minutes placing. Anchor to shape and port, keep waypoints relative to the anchors, and drop a waypoint only when it has become impossible, saying so rather than silently straightening the line. ## M3 · Undo that is provably complete Any sequence of operations, fully undone, returns the document exactly as it was. ### Steps 1. Give every mutation a command with an inverse And assert in a test that none is missing one. Files: `src/history/commands.ts`, `tests/inverses.test.ts` 2. Write the property test early, not last It is the cheapest way to find the operation you forgot. Files: `tests/undo-property.test.ts` 3. Restore selection as part of undo Undo that loses your selection makes you check what happened. Files: `src/history/stack.ts` ### Done when - [ ] A long random sequence undoes to a byte-identical document - [ ] Every command type has an inverse - [ ] Redo reproduces state and selection - [ ] Nothing mutates a document outside a command ### Watch out - Undo by snapshotting the whole document is correct and becomes unusable on a large diagram because every keystroke copies it, while undo written per operation is fast and is wrong in exactly the one operation somebody forgot to cover, which you will find after a user has lost an hour. Make every mutation go through a command with an inverse, assert in a test that every registered command type has one, and let a property test over random sequences find the gap instead of a user. ## M4 · An export you can reopen The SVG that goes to a colleague carries the document that made it. ### Steps 1. Embed the source document in the SVG The picture and the document should be one file. Files: `src/export/svg.ts`, `tests/export-roundtrip.test.ts` 2. Put the font trade-off in the dialogue Both consequences, in words, at the moment of choosing. Files: `src/ui/ExportDialog.tsx` 3. Show the pixel dimensions before exporting Nobody wants to guess what scale four means. Files: `src/export/raster.ts` ### Done when - [ ] An exported SVG reopens to an identical document - [ ] The dialogue states the font consequence - [ ] Raster dimensions match what was shown - [ ] Export works offline ### Watch out - An SVG whose text elements name a font renders with overlapping labels on any machine that lacks it, and converting text to outlines fixes the picture while making the words unselectable and the file much larger. This is a trade-off rather than a bug, so put it in the export dialogue with both consequences written out, and embed the source document so the editable version travels with whichever picture was chosen. ## M5 · Many diagrams, and a canvas that stays usable (production only) A folder of documents opens quickly and a large diagram still responds. ### Steps 1. Generate a large document and measure it The claimed limit should be a test, not an impression. Files: `tests/perf/large-doc.test.ts`, `scripts/generate-doc.ts` 2. Index shapes spatially for hit testing Dragging should not walk the document. Files: `src/canvas/hittest.ts` 3. Make bulk export report per document One bad file should not abort forty good ones. Files: `src/export/bulk.ts` ### Done when - [ ] The stated shape count stays interactive under measurement - [ ] The library lists a directory without parsing everything - [ ] Crash recovery offers the divergence choice - [ ] Bulk export reports failures individually ### Watch out - A canvas that is comfortable at fifty shapes and unusable at five hundred is the usual outcome, because everything re-renders on every pointer move and hit testing walks the whole document each time. Generate a document at the size you claim to support, measure the frame time while dragging, index shapes spatially for hit testing, and treat the number in the README as a promise a test checks rather than a hope.
# Operations · EdrawMax ## Backup The files, wherever the user chose to put them, which is the point of the whole persistence design: backup becomes somebody else's solved problem rather than a feature you had to write. The cache is never backed up. ## Restore Open a file and confirm it renders with connectors attached and labels in place. If the file opens and the diagram is right, there is nothing else to restore, which is the test of whether the format decision was correct. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor the checks live in the suite: the undo property test, the document validation run after every mutating operation, the export round trip, and the frame timing at the stated shape count. Watch the last one after any rendering change. ## Incident checklist The incident is a lost diagram, and almost always it is a diagram that only ever existed in browser storage. The save-state indicator and the insistence on a file early are the controls that prevent it, and the cache is what recovers the last few minutes when a tab dies. 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 - [ ] Clearing browser storage is confirmed to lose nothing saved to a file - [ ] No connector is confirmed to reference a deleted shape - [ ] A random command sequence is confirmed to undo byte-identically - [ ] An exported SVG is confirmed to reopen identically - [ ] The stated shape count is confirmed interactive under measurement ## Launch constraint Do not market omitted EdrawMax 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 indexed database used for crash recovery only. Evictable by design. CACHE_DB_NAME=diagram-cache # Required. How long a new document may go without a file before the editor insists. SAVE_PROMPT_SECONDS=120 # Required. After this long without a file write, the indicator changes state visibly. STALE_SAVE_WARN_MINUTES=10 # Required. The size the performance test generates and the README promises. TARGET_SHAPE_COUNT=500 # Required. Default raster scale. The dialogue shows resulting pixel dimensions. EXPORT_SCALE_DEFAULT=2
$ choose a build depth, inspect the files, then open the complete pack in your agent
EdrawMax: The shapes are easy; customers pay for low-friction editing, reliable exports, collaboration, and a canvas that never fights them.
xlarge template libraries
xhigh-fidelity import and export
xpresentation and enterprise controls
xmultiplayer cursors and comments
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
EdrawMax pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free trial | $0 | $0 | 2 built-in templates plus 2 local and 2 cloud templates; 500 AI tokens; 5 recycle-bin items retained 7 days; up to 60 shapes in a general diagram; 5 Visio/CAD imports; exported files are watermarked |
| semi-annual | custom | — | Desktop on up to 3 PCs plus 3 mobile devices and online access; 1 GB cloud storage |
| annual | — | $8.25 | Desktop on up to 3 PCs plus 3 mobile devices and online access; 1 GB cloud storage |
| perpetual | custom | — | Current major desktop version on up to 3 PCs; mobile/web access for 3 years; 10 GB cloud storage |
free tier2 built-in templates plus 2 local and 2 cloud templates; 500 AI tokens; 5 recycle-bin items/7-day retention; 60 shapes per general diagram; 5 Visio/CAD imports; watermark
billingsemi-annual + annual auto-renewing subscriptions; perpetual one-time license
hidden costsThe optional EdrawMax AI Assets checkbox starts with a 7-day trial and then auto-renews as a separate subscription at a price not cleanly exposed. Perpetual covers the purchased major desktop version and only 3 years of mobile/web access; subscription payments are not credited toward a perpetual license.
verified 2026-08-12 · source ↗
Vibecode EdrawMax
Kinda. The core of EdrawMax is buildable in a weekend with the prompt on this page, but there are real gaps: large template libraries, high-fidelity import and export. Read the honest list above before committing.
How much does EdrawMax cost?
EdrawMax's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing EdrawMax?
Honestly: large template libraries; high-fidelity import and export; presentation and enterprise controls; multiplayer cursors and comments. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to EdrawMax?
Yes: diagrams.net (The unglamorous diagram workhorse: huge template coverage, local files, imports, automatic layouts, and collaboration through shared storage.) yEd (Excellent automatic layout and plenty of business and technical diagrams; less template theatre, more getting boxes aligned.) The prompt is for when you want it exactly your way.