Vibecode SmartDraw
track this build4 phases, 12 steps, beginner friendly0%The visible business diagramming loop is buildable, but a credible replacement needs more than the first screen. SmartDraw 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 SmartDraw. 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 ===== # SmartDraw · indie build A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. 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, nothing leaves the browser | a diagram is a document and documents do not need an account | | Storage | IndexedDB holding a versioned document format plus periodic snapshots | a browser database is the only durable local store and it needs a schema version from day one | | Geometry | Edges reference shape identifiers and named ports, never absolute points | an edge pinned to coordinates detaches the first time somebody moves a box | | History | A command log of forward and inverse patches, grouped per user gesture | one drag that moves twelve shapes must undo once, not twelve times | | Export | SVG with every font and image embedded, and PNG rasterised from that SVG | an export that depends on the viewer's fonts is not an export | ## 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 and the build. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **A font you are licensed to embed** · free with an open licence - Why: Export quality depends on embedding the font, and embedding one whose licence forbids it is a problem you create for every user of your diagrams. - Get it: Choose an open licensed font family and keep the licence file with the project. - Verify: The licence permits embedding and the licence file is in the repository - [ ] **Two browsers from different engines** · free - Why: Canvas rasterisation, font loading and pointer events are where engines differ, and a diagram editor lives in all three. - Get it: Install a second browser with a different engine from your usual one. - Verify: Export and drag behave identically in both - [ ] **The document format, written down before any code** · free - Why: Changing the format after people have files is the expensive mistake in this category, and a written schema with a version number makes migration a task rather than an emergency. - Get it: Write the schema with its version field, and the rule for when the version increments. - Verify: The schema document exists and the code reads its version on open ## 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: - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. - 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 SmartDraw, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · SmartDraw 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 connectors stay attached to the shapes they were drawn to to replace SmartDraw. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server, nothing leaves the browser · a diagram is a document and documents do not need an account - Storage: IndexedDB holding a versioned document format plus periodic snapshots · a browser database is the only durable local store and it needs a schema version from day one - Geometry: Edges reference shape identifiers and named ports, never absolute points · an edge pinned to coordinates detaches the first time somebody moves a box - History: A command log of forward and inverse patches, grouped per user gesture · one drag that moves twelve shapes must undo once, not twelve times - Export: SVG with every font and image embedded, and PNG rasterised from that SVG · an export that depends on the viewer's fonts is not an export ### Data model (create this before Phase 1) - documents(id, title, schema_version, created_at, updated_at, canvas_w, canvas_h) - nodes(id, doc_id, kind, x, y, w, h, rotation, text, style_json) - ports(node_id, name, rel_x, rel_y) · anchors expressed as fractions of the shape box so resizing moves them correctly - edges(id, doc_id, from_node, from_port, to_node, to_port, route_mode, waypoints_json, style_json) · endpoints are references, never coordinates - commands(doc_id, ordinal, label, forward_json, inverse_json, at) · the undo stack, persisted so a reload does not erase it - assets(id, doc_id, kind, mime, bytes, sha256) · fonts and images stored in the document so an export is self-contained - snapshots(doc_id, at, blob) · a periodic whole-document copy, because a command log with one corrupt entry is a lost diagram ### Phase 1 · An edge that points at a shape Build: Define shapes with named ports whose positions are fractions of the shape box. Edges store the shape identifier and the port name at each end. Recompute an edge's endpoints from the current geometry at render time. Deleting a shape either deletes its edges or leaves them dangling with a visible marker, and whichever you choose is undoable as one action. Trap: Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. Done when: Moving or resizing a shape keeps every attached edge touching it, tested by asserting endpoint positions after a programmatic move. Port positions scale correctly when a shape is resized non-uniformly, tested. Deleting a shape with three edges is one undoable action that restores all four objects. No edge in the document stores an absolute endpoint coordinate, verified in the schema. Do not build yet: No undo stack, routing, styling or export yet. ### Phase 2 · Undo that covers everything Build: Model every mutation as a command with a forward and an inverse patch. Group a whole gesture into one entry: a drag of a multiple selection, a paste, a style change across shapes. Persist the stack with the document. Restore the selection state that existed before an undone action, because undoing without restoring the selection leaves people lost. Trap: Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. Done when: Two hundred operations leave the history under a stated memory budget, measured. A drag of a ten-shape selection undoes in one step and redoes identically, tested. Reloading the page preserves the undo stack and undo still works across the reload. Undoing restores the selection that was active before the action, tested. Do not build yet: No routing, keyboard work or export yet. ### Phase 3 · A canvas that does not fight you Build: Hit testing against shape geometry rather than bounding boxes, marquee selection, snapping to shape edges and centres with visible guides, arrow-key nudging, and text editing in place with wrapping inside the shape. Keep interaction responsive on a document with several hundred objects by redrawing only what changed. Trap: Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. Done when: Clicking the visible gap inside a rotated diamond's bounding box selects what is behind it, tested. Snapping shows a guide and can be suppressed with a held modifier, and the snap distance is in configuration. Dragging stays responsive on a document of several hundred shapes, measured against a stated frame budget. Text wraps inside the shape and the shape can grow to fit, with the choice remembered per shape. Do not build yet: No routing or export yet. ### Phase 4 · Routing that goes around things Build: Offer straight, orthogonal and curved routing per edge. Route orthogonal edges around obstacles on a grid derived from the shapes. When a user drags a waypoint, that edge becomes manually routed and stops being recomputed until they reset it. Recompute only edges whose endpoints or obstacles changed, never the whole document. Trap: Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. Done when: An orthogonal edge routes around an intervening shape rather than through it, tested with a placed obstacle. Dragging a waypoint marks the edge manual and a later shape move does not rewrite it, tested. Resetting a manual edge returns it to automatic routing. A drag on a document with many edges recomputes only the affected ones, asserted by counting recomputations. Do not build yet: No export or file format work yet. ### Phase 5 · An export that stands on its own Build: Embed every font subset and every image into the SVG as data URIs before exporting. Rasterise PNG by drawing that self-contained SVG onto a canvas. Version the document format and write an importer for the previous version. Export and import the whole document as JSON with assets. Trap: Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. Done when: An exported SVG opened on a machine without the diagram's fonts renders with the same glyphs, tested by removing the font locally. PNG export succeeds on a document containing an embedded image, tested for the absence of a security error. A document saved under the previous format version opens and is migrated, tested. A JSON export reimports into an empty browser profile with assets and history intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time collaboration, which is a different application built around a conflict-free data type rather than a feature you add to this one. - Importing other vendors' diagram formats, which is reverse engineering a moving target and never quite works. - A large template and stencil library, which is content production rather than engineering and is a real part of what the subscription buys. - Automatic layout of an entire diagram, which is a satisfying research problem and a poor use of the weekend this build has. ### README must contain - That edges reference shapes and ports rather than coordinates, and what that means for anyone extending the format - That undo is a patch log persisted with the document, and the memory budget it is held to - That exports inline every font and image, and that a PNG cannot be produced from an SVG referencing external resources - The document format version, what changed in it, and how migration runs on open - Where the data lives in the browser, how to export everything, and that clearing site data deletes it ===== AGENTS.md ===== # Agent instructions · SmartDraw indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server, nothing leaves the browser, IndexedDB holding a versioned document format plus periodic snapshots, Edges reference shape identifiers and named ports, never absolute points, A command log of forward and inverse patches, grouped per user gesture, SVG with every font and image embedded, and PNG rasterised from that SVG. 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 - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ===== BUILD_PLAN.md ===== # Build plan · SmartDraw A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An edge that points at a shape Moving, resizing or deleting a shape does the right thing to everything attached to it. ### Steps 1. Express ports as fractions of the shape box Absolute offsets break on the first resize. Files: `src/model/ports.ts`, `tests/geometry.test.ts` 2. Compute endpoints at render, store references The stored document should be the truth, not a cache of pixels. Files: `src/model/edges.ts` 3. Decide what a deleted shape does to its edges Both answers are fine; silently leaving arrows to nowhere is not. Files: `src/model/document.ts` ### Done when - [ ] Edges stay attached through moves and resizes - [ ] Ports scale correctly on a non-uniform resize - [ ] Deleting a shape with edges undoes as one action - [ ] No absolute endpoint coordinates are stored ### Watch out - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. ## Phase 2 · Undo that covers everything Every change is one entry on a stack that survives a reload, including changes made while you were not looking. ### Steps 1. Patches, not snapshots Snapshots are for recovery; a history of them is a memory leak. Files: `src/history/commands.ts`, `tests/history.test.ts` 2. Group a gesture into one entry Twelve undos for one drag is the same as no undo. Files: `src/history/transaction.ts` 3. Persist the stack and restore selection A reload should not be a wall you cannot step back through. Files: `src/history/persist.ts` ### Done when - [ ] History memory stays inside its budget after 200 operations - [ ] A multi-select drag undoes in one step - [ ] Undo survives a reload - [ ] Selection is restored when an action is undone ### Watch out - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. ## Phase 3 · A canvas that does not fight you Selecting, dragging, snapping and typing feel right at the size of diagram people actually draw. ### Steps 1. Hit test against real geometry Bounding boxes are why cheap editors feel cheap. Files: `src/canvas/hit.ts`, `tests/hit.test.ts` 2. Snap with visible guides and an escape hatch Snapping you cannot switch off is a trap. Files: `src/canvas/snap.ts` 3. Redraw only what changed Several hundred objects is a normal diagram, not a stress test. Files: `src/canvas/render.ts` ### Done when - [ ] Rotated shapes do not steal clicks from behind them - [ ] Snap guides appear and can be suppressed - [ ] Dragging holds the frame budget at several hundred objects - [ ] Text wraps inside shapes and growth is remembered ### Watch out - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. ## Phase 4 · Routing that goes around things Connectors take a sensible path and keep any path the user set by hand. ### Steps 1. Route on a grid derived from the shapes A general path finder is overkill; obstacle corners are enough. Files: `src/routing/orthogonal.ts`, `tests/routing.test.ts` 2. Let a hand-placed waypoint win permanently The user moved it because your router was wrong. Files: `src/routing/manual.ts` 3. Recompute only affected edges, after the gesture Routing during a drag is how editors get a reputation. Files: `src/routing/scheduler.ts` ### Done when - [ ] Orthogonal edges avoid intervening shapes - [ ] Manual waypoints survive later shape moves - [ ] Resetting returns an edge to automatic routing - [ ] Only affected edges recompute during a drag ### Watch out - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ## Not in this build - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. ## After v1, if you want it - A read-only viewer link that renders an exported document, which is most of what sharing actually means - Layers, which change selection and hit testing enough to be worth doing after the basics are solid - A text-to-diagram importer for one simple syntax, which is a better use of a model than generating shapes freehand ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Shown in the tab and in exported file names. VITE_APP_TITLE=Diagrams # Required. Snapping distance. People disagree about this, so it is configuration. VITE_SNAP_DISTANCE_PX=8 # Required. The undo stack is trimmed rather than allowed to grow without limit. VITE_HISTORY_MEMORY_BUDGET_MB=24 # Required. How often a whole-document snapshot is written for recovery. VITE_AUTOSNAPSHOT_SECONDS=120 # Required. Written into every document and checked on open. VITE_SCHEMA_VERSION=3
You are building a lean indie version of SmartDraw. 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 ===== # SmartDraw · indie build A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. 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, nothing leaves the browser | a diagram is a document and documents do not need an account | | Storage | IndexedDB holding a versioned document format plus periodic snapshots | a browser database is the only durable local store and it needs a schema version from day one | | Geometry | Edges reference shape identifiers and named ports, never absolute points | an edge pinned to coordinates detaches the first time somebody moves a box | | History | A command log of forward and inverse patches, grouped per user gesture | one drag that moves twelve shapes must undo once, not twelve times | | Export | SVG with every font and image embedded, and PNG rasterised from that SVG | an export that depends on the viewer's fonts is not an export | ## 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 and the build. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **A font you are licensed to embed** · free with an open licence - Why: Export quality depends on embedding the font, and embedding one whose licence forbids it is a problem you create for every user of your diagrams. - Get it: Choose an open licensed font family and keep the licence file with the project. - Verify: The licence permits embedding and the licence file is in the repository - [ ] **Two browsers from different engines** · free - Why: Canvas rasterisation, font loading and pointer events are where engines differ, and a diagram editor lives in all three. - Get it: Install a second browser with a different engine from your usual one. - Verify: Export and drag behave identically in both - [ ] **The document format, written down before any code** · free - Why: Changing the format after people have files is the expensive mistake in this category, and a written schema with a version number makes migration a task rather than an emergency. - Get it: Write the schema with its version field, and the rule for when the version increments. - Verify: The schema document exists and the code reads its version on open ## 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: - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. - 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 SmartDraw, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · SmartDraw 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 connectors stay attached to the shapes they were drawn to to replace SmartDraw. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server, nothing leaves the browser · a diagram is a document and documents do not need an account - Storage: IndexedDB holding a versioned document format plus periodic snapshots · a browser database is the only durable local store and it needs a schema version from day one - Geometry: Edges reference shape identifiers and named ports, never absolute points · an edge pinned to coordinates detaches the first time somebody moves a box - History: A command log of forward and inverse patches, grouped per user gesture · one drag that moves twelve shapes must undo once, not twelve times - Export: SVG with every font and image embedded, and PNG rasterised from that SVG · an export that depends on the viewer's fonts is not an export ### Data model (create this before Phase 1) - documents(id, title, schema_version, created_at, updated_at, canvas_w, canvas_h) - nodes(id, doc_id, kind, x, y, w, h, rotation, text, style_json) - ports(node_id, name, rel_x, rel_y) · anchors expressed as fractions of the shape box so resizing moves them correctly - edges(id, doc_id, from_node, from_port, to_node, to_port, route_mode, waypoints_json, style_json) · endpoints are references, never coordinates - commands(doc_id, ordinal, label, forward_json, inverse_json, at) · the undo stack, persisted so a reload does not erase it - assets(id, doc_id, kind, mime, bytes, sha256) · fonts and images stored in the document so an export is self-contained - snapshots(doc_id, at, blob) · a periodic whole-document copy, because a command log with one corrupt entry is a lost diagram ### Phase 1 · An edge that points at a shape Build: Define shapes with named ports whose positions are fractions of the shape box. Edges store the shape identifier and the port name at each end. Recompute an edge's endpoints from the current geometry at render time. Deleting a shape either deletes its edges or leaves them dangling with a visible marker, and whichever you choose is undoable as one action. Trap: Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. Done when: Moving or resizing a shape keeps every attached edge touching it, tested by asserting endpoint positions after a programmatic move. Port positions scale correctly when a shape is resized non-uniformly, tested. Deleting a shape with three edges is one undoable action that restores all four objects. No edge in the document stores an absolute endpoint coordinate, verified in the schema. Do not build yet: No undo stack, routing, styling or export yet. ### Phase 2 · Undo that covers everything Build: Model every mutation as a command with a forward and an inverse patch. Group a whole gesture into one entry: a drag of a multiple selection, a paste, a style change across shapes. Persist the stack with the document. Restore the selection state that existed before an undone action, because undoing without restoring the selection leaves people lost. Trap: Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. Done when: Two hundred operations leave the history under a stated memory budget, measured. A drag of a ten-shape selection undoes in one step and redoes identically, tested. Reloading the page preserves the undo stack and undo still works across the reload. Undoing restores the selection that was active before the action, tested. Do not build yet: No routing, keyboard work or export yet. ### Phase 3 · A canvas that does not fight you Build: Hit testing against shape geometry rather than bounding boxes, marquee selection, snapping to shape edges and centres with visible guides, arrow-key nudging, and text editing in place with wrapping inside the shape. Keep interaction responsive on a document with several hundred objects by redrawing only what changed. Trap: Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. Done when: Clicking the visible gap inside a rotated diamond's bounding box selects what is behind it, tested. Snapping shows a guide and can be suppressed with a held modifier, and the snap distance is in configuration. Dragging stays responsive on a document of several hundred shapes, measured against a stated frame budget. Text wraps inside the shape and the shape can grow to fit, with the choice remembered per shape. Do not build yet: No routing or export yet. ### Phase 4 · Routing that goes around things Build: Offer straight, orthogonal and curved routing per edge. Route orthogonal edges around obstacles on a grid derived from the shapes. When a user drags a waypoint, that edge becomes manually routed and stops being recomputed until they reset it. Recompute only edges whose endpoints or obstacles changed, never the whole document. Trap: Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. Done when: An orthogonal edge routes around an intervening shape rather than through it, tested with a placed obstacle. Dragging a waypoint marks the edge manual and a later shape move does not rewrite it, tested. Resetting a manual edge returns it to automatic routing. A drag on a document with many edges recomputes only the affected ones, asserted by counting recomputations. Do not build yet: No export or file format work yet. ### Phase 5 · An export that stands on its own Build: Embed every font subset and every image into the SVG as data URIs before exporting. Rasterise PNG by drawing that self-contained SVG onto a canvas. Version the document format and write an importer for the previous version. Export and import the whole document as JSON with assets. Trap: Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. Done when: An exported SVG opened on a machine without the diagram's fonts renders with the same glyphs, tested by removing the font locally. PNG export succeeds on a document containing an embedded image, tested for the absence of a security error. A document saved under the previous format version opens and is migrated, tested. A JSON export reimports into an empty browser profile with assets and history intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time collaboration, which is a different application built around a conflict-free data type rather than a feature you add to this one. - Importing other vendors' diagram formats, which is reverse engineering a moving target and never quite works. - A large template and stencil library, which is content production rather than engineering and is a real part of what the subscription buys. - Automatic layout of an entire diagram, which is a satisfying research problem and a poor use of the weekend this build has. ### README must contain - That edges reference shapes and ports rather than coordinates, and what that means for anyone extending the format - That undo is a patch log persisted with the document, and the memory budget it is held to - That exports inline every font and image, and that a PNG cannot be produced from an SVG referencing external resources - The document format version, what changed in it, and how migration runs on open - Where the data lives in the browser, how to export everything, and that clearing site data deletes it ===== AGENTS.md ===== # Agent instructions · SmartDraw indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server, nothing leaves the browser, IndexedDB holding a versioned document format plus periodic snapshots, Edges reference shape identifiers and named ports, never absolute points, A command log of forward and inverse patches, grouped per user gesture, SVG with every font and image embedded, and PNG rasterised from that SVG. 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 - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ===== BUILD_PLAN.md ===== # Build plan · SmartDraw A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An edge that points at a shape Moving, resizing or deleting a shape does the right thing to everything attached to it. ### Steps 1. Express ports as fractions of the shape box Absolute offsets break on the first resize. Files: `src/model/ports.ts`, `tests/geometry.test.ts` 2. Compute endpoints at render, store references The stored document should be the truth, not a cache of pixels. Files: `src/model/edges.ts` 3. Decide what a deleted shape does to its edges Both answers are fine; silently leaving arrows to nowhere is not. Files: `src/model/document.ts` ### Done when - [ ] Edges stay attached through moves and resizes - [ ] Ports scale correctly on a non-uniform resize - [ ] Deleting a shape with edges undoes as one action - [ ] No absolute endpoint coordinates are stored ### Watch out - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. ## Phase 2 · Undo that covers everything Every change is one entry on a stack that survives a reload, including changes made while you were not looking. ### Steps 1. Patches, not snapshots Snapshots are for recovery; a history of them is a memory leak. Files: `src/history/commands.ts`, `tests/history.test.ts` 2. Group a gesture into one entry Twelve undos for one drag is the same as no undo. Files: `src/history/transaction.ts` 3. Persist the stack and restore selection A reload should not be a wall you cannot step back through. Files: `src/history/persist.ts` ### Done when - [ ] History memory stays inside its budget after 200 operations - [ ] A multi-select drag undoes in one step - [ ] Undo survives a reload - [ ] Selection is restored when an action is undone ### Watch out - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. ## Phase 3 · A canvas that does not fight you Selecting, dragging, snapping and typing feel right at the size of diagram people actually draw. ### Steps 1. Hit test against real geometry Bounding boxes are why cheap editors feel cheap. Files: `src/canvas/hit.ts`, `tests/hit.test.ts` 2. Snap with visible guides and an escape hatch Snapping you cannot switch off is a trap. Files: `src/canvas/snap.ts` 3. Redraw only what changed Several hundred objects is a normal diagram, not a stress test. Files: `src/canvas/render.ts` ### Done when - [ ] Rotated shapes do not steal clicks from behind them - [ ] Snap guides appear and can be suppressed - [ ] Dragging holds the frame budget at several hundred objects - [ ] Text wraps inside shapes and growth is remembered ### Watch out - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. ## Phase 4 · Routing that goes around things Connectors take a sensible path and keep any path the user set by hand. ### Steps 1. Route on a grid derived from the shapes A general path finder is overkill; obstacle corners are enough. Files: `src/routing/orthogonal.ts`, `tests/routing.test.ts` 2. Let a hand-placed waypoint win permanently The user moved it because your router was wrong. Files: `src/routing/manual.ts` 3. Recompute only affected edges, after the gesture Routing during a drag is how editors get a reputation. Files: `src/routing/scheduler.ts` ### Done when - [ ] Orthogonal edges avoid intervening shapes - [ ] Manual waypoints survive later shape moves - [ ] Resetting returns an edge to automatic routing - [ ] Only affected edges recompute during a drag ### Watch out - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ## Not in this build - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. ## After v1, if you want it - A read-only viewer link that renders an exported document, which is most of what sharing actually means - Layers, which change selection and hit testing enough to be worth doing after the basics are solid - A text-to-diagram importer for one simple syntax, which is a better use of a model than generating shapes freehand ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Shown in the tab and in exported file names. VITE_APP_TITLE=Diagrams # Required. Snapping distance. People disagree about this, so it is configuration. VITE_SNAP_DISTANCE_PX=8 # Required. The undo stack is trimmed rather than allowed to grow without limit. VITE_HISTORY_MEMORY_BUDGET_MB=24 # Required. How often a whole-document snapshot is written for recovery. VITE_AUTOSNAPSHOT_SECONDS=120 # Required. Written into every document and checked on open. VITE_SCHEMA_VERSION=3
You are building a production product version of SmartDraw. 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 ===== # SmartDraw · product brief ## Problem The visible business diagramming loop is buildable, but a credible replacement needs more than the first screen. SmartDraw 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 where arrows stay attached, one gesture is one undo that survives a reload, connectors route around shapes unless you moved them yourself, and an exported file looks the same on a machine that has none of your fonts. ## 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 - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments ## Success criteria - Edges are confirmed attached after programmatic moves and resizes - A multi-select drag is confirmed to undo in one step across a reload - Rotated shapes are confirmed not to steal clicks from behind them - PNG export is confirmed to succeed on a document containing images - An exported SVG is confirmed identical without the local fonts ===== BRIEF.md ===== # Build brief · SmartDraw 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 connectors stay attached to the shapes they were drawn to to replace SmartDraw. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server, nothing leaves the browser · a diagram is a document and documents do not need an account - Storage: IndexedDB holding a versioned document format plus periodic snapshots · a browser database is the only durable local store and it needs a schema version from day one - Geometry: Edges reference shape identifiers and named ports, never absolute points · an edge pinned to coordinates detaches the first time somebody moves a box - History: A command log of forward and inverse patches, grouped per user gesture · one drag that moves twelve shapes must undo once, not twelve times - Export: SVG with every font and image embedded, and PNG rasterised from that SVG · an export that depends on the viewer's fonts is not an export ### Data model (create this before Phase 1) - documents(id, title, schema_version, created_at, updated_at, canvas_w, canvas_h) - nodes(id, doc_id, kind, x, y, w, h, rotation, text, style_json) - ports(node_id, name, rel_x, rel_y) · anchors expressed as fractions of the shape box so resizing moves them correctly - edges(id, doc_id, from_node, from_port, to_node, to_port, route_mode, waypoints_json, style_json) · endpoints are references, never coordinates - commands(doc_id, ordinal, label, forward_json, inverse_json, at) · the undo stack, persisted so a reload does not erase it - assets(id, doc_id, kind, mime, bytes, sha256) · fonts and images stored in the document so an export is self-contained - snapshots(doc_id, at, blob) · a periodic whole-document copy, because a command log with one corrupt entry is a lost diagram ### Phase 1 · An edge that points at a shape Build: Define shapes with named ports whose positions are fractions of the shape box. Edges store the shape identifier and the port name at each end. Recompute an edge's endpoints from the current geometry at render time. Deleting a shape either deletes its edges or leaves them dangling with a visible marker, and whichever you choose is undoable as one action. Trap: Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. Done when: Moving or resizing a shape keeps every attached edge touching it, tested by asserting endpoint positions after a programmatic move. Port positions scale correctly when a shape is resized non-uniformly, tested. Deleting a shape with three edges is one undoable action that restores all four objects. No edge in the document stores an absolute endpoint coordinate, verified in the schema. Do not build yet: No undo stack, routing, styling or export yet. ### Phase 2 · Undo that covers everything Build: Model every mutation as a command with a forward and an inverse patch. Group a whole gesture into one entry: a drag of a multiple selection, a paste, a style change across shapes. Persist the stack with the document. Restore the selection state that existed before an undone action, because undoing without restoring the selection leaves people lost. Trap: Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. Done when: Two hundred operations leave the history under a stated memory budget, measured. A drag of a ten-shape selection undoes in one step and redoes identically, tested. Reloading the page preserves the undo stack and undo still works across the reload. Undoing restores the selection that was active before the action, tested. Do not build yet: No routing, keyboard work or export yet. ### Phase 3 · A canvas that does not fight you Build: Hit testing against shape geometry rather than bounding boxes, marquee selection, snapping to shape edges and centres with visible guides, arrow-key nudging, and text editing in place with wrapping inside the shape. Keep interaction responsive on a document with several hundred objects by redrawing only what changed. Trap: Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. Done when: Clicking the visible gap inside a rotated diamond's bounding box selects what is behind it, tested. Snapping shows a guide and can be suppressed with a held modifier, and the snap distance is in configuration. Dragging stays responsive on a document of several hundred shapes, measured against a stated frame budget. Text wraps inside the shape and the shape can grow to fit, with the choice remembered per shape. Do not build yet: No routing or export yet. ### Phase 4 · Routing that goes around things Build: Offer straight, orthogonal and curved routing per edge. Route orthogonal edges around obstacles on a grid derived from the shapes. When a user drags a waypoint, that edge becomes manually routed and stops being recomputed until they reset it. Recompute only edges whose endpoints or obstacles changed, never the whole document. Trap: Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. Done when: An orthogonal edge routes around an intervening shape rather than through it, tested with a placed obstacle. Dragging a waypoint marks the edge manual and a later shape move does not rewrite it, tested. Resetting a manual edge returns it to automatic routing. A drag on a document with many edges recomputes only the affected ones, asserted by counting recomputations. Do not build yet: No export or file format work yet. ### Phase 5 · An export that stands on its own Build: Embed every font subset and every image into the SVG as data URIs before exporting. Rasterise PNG by drawing that self-contained SVG onto a canvas. Version the document format and write an importer for the previous version. Export and import the whole document as JSON with assets. Trap: Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. Done when: An exported SVG opened on a machine without the diagram's fonts renders with the same glyphs, tested by removing the font locally. PNG export succeeds on a document containing an embedded image, tested for the absence of a security error. A document saved under the previous format version opens and is migrated, tested. A JSON export reimports into an empty browser profile with assets and history intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time collaboration, which is a different application built around a conflict-free data type rather than a feature you add to this one. - Importing other vendors' diagram formats, which is reverse engineering a moving target and never quite works. - A large template and stencil library, which is content production rather than engineering and is a real part of what the subscription buys. - Automatic layout of an entire diagram, which is a satisfying research problem and a poor use of the weekend this build has. ### README must contain - That edges reference shapes and ports rather than coordinates, and what that means for anyone extending the format - That undo is a patch log persisted with the document, and the memory budget it is held to - That exports inline every font and image, and that a PNG cannot be produced from an SVG referencing external resources - The document format version, what changed in it, and how migration runs on open - Where the data lives in the browser, how to export everything, and that clearing site data deletes it ===== ARCHITECTURE.md ===== # Architecture · SmartDraw ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server, nothing leaves the browser | a diagram is a document and documents do not need an account | | Storage | IndexedDB holding a versioned document format plus periodic snapshots | a browser database is the only durable local store and it needs a schema version from day one | | Geometry | Edges reference shape identifiers and named ports, never absolute points | an edge pinned to coordinates detaches the first time somebody moves a box | | History | A command log of forward and inverse patches, grouped per user gesture | one drag that moves twelve shapes must undo once, not twelve times | | Export | SVG with every font and image embedded, and PNG rasterised from that SVG | an export that depends on the viewer's fonts is not an export | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Model | Shapes, ports, edges and the format version. | Any store, provided edges keep references rather than coordinates. | | History | Command patches and gesture grouping. | Any scheme, provided it is patches rather than document snapshots. | | Canvas | Hit testing, snapping, rendering. | Any renderer, provided hit testing uses real geometry. | | Routing | Automatic paths and manual waypoints. | Any router, provided manual waypoints are never overwritten. | | Export | Inlining, subsetting, rasterising, migration. | Any pipeline, provided nothing external survives into the output. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_APP_TITLE` · required · Shown in the tab and in exported file names. - `VITE_SNAP_DISTANCE_PX` · required · Snapping distance. People disagree about this, so it is configuration. - `VITE_HISTORY_MEMORY_BUDGET_MB` · required · The undo stack is trimmed rather than allowed to grow without limit. - `VITE_AUTOSNAPSHOT_SECONDS` · required · How often a whole-document snapshot is written for recovery. - `VITE_SCHEMA_VERSION` · required · Written into every document and checked on open. ## 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 · SmartDraw product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, no server, nothing leaves the browser, IndexedDB holding a versioned document format plus periodic snapshots, Edges reference shape identifiers and named ports, never absolute points, A command log of forward and inverse patches, grouped per user gesture, SVG with every font and image embedded, and PNG rasterised from that SVG. - 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 - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. - Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. ===== MILESTONES.md ===== # Delivery milestones · SmartDraw Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An edge that points at a shape Moving, resizing or deleting a shape does the right thing to everything attached to it. ### Steps 1. Express ports as fractions of the shape box Absolute offsets break on the first resize. Files: `src/model/ports.ts`, `tests/geometry.test.ts` 2. Compute endpoints at render, store references The stored document should be the truth, not a cache of pixels. Files: `src/model/edges.ts` 3. Decide what a deleted shape does to its edges Both answers are fine; silently leaving arrows to nowhere is not. Files: `src/model/document.ts` ### Done when - [ ] Edges stay attached through moves and resizes - [ ] Ports scale correctly on a non-uniform resize - [ ] Deleting a shape with edges undoes as one action - [ ] No absolute endpoint coordinates are stored ### Watch out - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. ## M2 · Undo that covers everything Every change is one entry on a stack that survives a reload, including changes made while you were not looking. ### Steps 1. Patches, not snapshots Snapshots are for recovery; a history of them is a memory leak. Files: `src/history/commands.ts`, `tests/history.test.ts` 2. Group a gesture into one entry Twelve undos for one drag is the same as no undo. Files: `src/history/transaction.ts` 3. Persist the stack and restore selection A reload should not be a wall you cannot step back through. Files: `src/history/persist.ts` ### Done when - [ ] History memory stays inside its budget after 200 operations - [ ] A multi-select drag undoes in one step - [ ] Undo survives a reload - [ ] Selection is restored when an action is undone ### Watch out - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. ## M3 · A canvas that does not fight you Selecting, dragging, snapping and typing feel right at the size of diagram people actually draw. ### Steps 1. Hit test against real geometry Bounding boxes are why cheap editors feel cheap. Files: `src/canvas/hit.ts`, `tests/hit.test.ts` 2. Snap with visible guides and an escape hatch Snapping you cannot switch off is a trap. Files: `src/canvas/snap.ts` 3. Redraw only what changed Several hundred objects is a normal diagram, not a stress test. Files: `src/canvas/render.ts` ### Done when - [ ] Rotated shapes do not steal clicks from behind them - [ ] Snap guides appear and can be suppressed - [ ] Dragging holds the frame budget at several hundred objects - [ ] Text wraps inside shapes and growth is remembered ### Watch out - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. ## M4 · Routing that goes around things Connectors take a sensible path and keep any path the user set by hand. ### Steps 1. Route on a grid derived from the shapes A general path finder is overkill; obstacle corners are enough. Files: `src/routing/orthogonal.ts`, `tests/routing.test.ts` 2. Let a hand-placed waypoint win permanently The user moved it because your router was wrong. Files: `src/routing/manual.ts` 3. Recompute only affected edges, after the gesture Routing during a drag is how editors get a reputation. Files: `src/routing/scheduler.ts` ### Done when - [ ] Orthogonal edges avoid intervening shapes - [ ] Manual waypoints survive later shape moves - [ ] Resetting returns an edge to automatic routing - [ ] Only affected edges recompute during a drag ### Watch out - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ## M5 · An export that stands on its own (production only) The exported file looks the same on a machine that has none of your fonts, and the document format is portable. ### Steps 1. Inline fonts and images before rasterising An external reference is what makes the canvas refuse to give you the image. Files: `src/export/inline.ts`, `tests/export.test.ts` 2. Subset the font rather than embedding all of it A whole font is megabytes and a diagram uses forty glyphs. Files: `src/export/fonts.ts` 3. Version the format and write the migration The second version is the one that loses people's files. Files: `src/model/migrate.ts` ### Done when - [ ] SVG renders identically without the local fonts - [ ] PNG export succeeds on a document with images - [ ] A previous-version document migrates on open - [ ] JSON export reimports into an empty profile ### Watch out - Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. ===== OPERATIONS.md ===== # Operations · SmartDraw ## Backup Browser storage is deleted by the browser without asking, so the backup story is the export: a scheduled reminder to export the whole document set as JSON, and a note in the interface saying when the last export happened. ## Restore Import the JSON into a fresh profile and confirm document count, asset checksums and that a sample diagram exports to an identical SVG. If the assets did not come back, exports will start depending on local fonts again and you will not notice immediately. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor, the measurements live in the suite and the interface: history memory, frame time during a drag on a large document, export failure rate, and the age of the last full export. ## Incident checklist The two incidents are a lost document and an export that silently changed appearance. The first is answered by snapshots plus reminded exports, the second by the test that renders without the local fonts installed. 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 - [ ] Edges are confirmed attached after programmatic moves and resizes - [ ] A multi-select drag is confirmed to undo in one step across a reload - [ ] Rotated shapes are confirmed not to steal clicks from behind them - [ ] PNG export is confirmed to succeed on a document containing images - [ ] An exported SVG is confirmed identical without the local fonts ## Launch constraint Do not market omitted SmartDraw 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. Shown in the tab and in exported file names. VITE_APP_TITLE=Diagrams # Required. Snapping distance. People disagree about this, so it is configuration. VITE_SNAP_DISTANCE_PX=8 # Required. The undo stack is trimmed rather than allowed to grow without limit. VITE_HISTORY_MEMORY_BUDGET_MB=24 # Required. How often a whole-document snapshot is written for recovery. VITE_AUTOSNAPSHOT_SECONDS=120 # Required. Written into every document and checked on open. VITE_SCHEMA_VERSION=3
# SmartDraw · indie build A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. 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, nothing leaves the browser | a diagram is a document and documents do not need an account | | Storage | IndexedDB holding a versioned document format plus periodic snapshots | a browser database is the only durable local store and it needs a schema version from day one | | Geometry | Edges reference shape identifiers and named ports, never absolute points | an edge pinned to coordinates detaches the first time somebody moves a box | | History | A command log of forward and inverse patches, grouped per user gesture | one drag that moves twelve shapes must undo once, not twelve times | | Export | SVG with every font and image embedded, and PNG rasterised from that SVG | an export that depends on the viewer's fonts is not an export | ## 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 and the build. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **A font you are licensed to embed** · free with an open licence - Why: Export quality depends on embedding the font, and embedding one whose licence forbids it is a problem you create for every user of your diagrams. - Get it: Choose an open licensed font family and keep the licence file with the project. - Verify: The licence permits embedding and the licence file is in the repository - [ ] **Two browsers from different engines** · free - Why: Canvas rasterisation, font loading and pointer events are where engines differ, and a diagram editor lives in all three. - Get it: Install a second browser with a different engine from your usual one. - Verify: Export and drag behave identically in both - [ ] **The document format, written down before any code** · free - Why: Changing the format after people have files is the expensive mistake in this category, and a written schema with a version number makes migration a task rather than an emergency. - Get it: Write the schema with its version field, and the rule for when the version increments. - Verify: The schema document exists and the code reads its version on open ## 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: - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. - 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 SmartDraw, and the README should say so rather than pretend.
# Build brief · SmartDraw 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 connectors stay attached to the shapes they were drawn to to replace SmartDraw. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server, nothing leaves the browser · a diagram is a document and documents do not need an account - Storage: IndexedDB holding a versioned document format plus periodic snapshots · a browser database is the only durable local store and it needs a schema version from day one - Geometry: Edges reference shape identifiers and named ports, never absolute points · an edge pinned to coordinates detaches the first time somebody moves a box - History: A command log of forward and inverse patches, grouped per user gesture · one drag that moves twelve shapes must undo once, not twelve times - Export: SVG with every font and image embedded, and PNG rasterised from that SVG · an export that depends on the viewer's fonts is not an export ### Data model (create this before Phase 1) - documents(id, title, schema_version, created_at, updated_at, canvas_w, canvas_h) - nodes(id, doc_id, kind, x, y, w, h, rotation, text, style_json) - ports(node_id, name, rel_x, rel_y) · anchors expressed as fractions of the shape box so resizing moves them correctly - edges(id, doc_id, from_node, from_port, to_node, to_port, route_mode, waypoints_json, style_json) · endpoints are references, never coordinates - commands(doc_id, ordinal, label, forward_json, inverse_json, at) · the undo stack, persisted so a reload does not erase it - assets(id, doc_id, kind, mime, bytes, sha256) · fonts and images stored in the document so an export is self-contained - snapshots(doc_id, at, blob) · a periodic whole-document copy, because a command log with one corrupt entry is a lost diagram ### Phase 1 · An edge that points at a shape Build: Define shapes with named ports whose positions are fractions of the shape box. Edges store the shape identifier and the port name at each end. Recompute an edge's endpoints from the current geometry at render time. Deleting a shape either deletes its edges or leaves them dangling with a visible marker, and whichever you choose is undoable as one action. Trap: Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. Done when: Moving or resizing a shape keeps every attached edge touching it, tested by asserting endpoint positions after a programmatic move. Port positions scale correctly when a shape is resized non-uniformly, tested. Deleting a shape with three edges is one undoable action that restores all four objects. No edge in the document stores an absolute endpoint coordinate, verified in the schema. Do not build yet: No undo stack, routing, styling or export yet. ### Phase 2 · Undo that covers everything Build: Model every mutation as a command with a forward and an inverse patch. Group a whole gesture into one entry: a drag of a multiple selection, a paste, a style change across shapes. Persist the stack with the document. Restore the selection state that existed before an undone action, because undoing without restoring the selection leaves people lost. Trap: Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. Done when: Two hundred operations leave the history under a stated memory budget, measured. A drag of a ten-shape selection undoes in one step and redoes identically, tested. Reloading the page preserves the undo stack and undo still works across the reload. Undoing restores the selection that was active before the action, tested. Do not build yet: No routing, keyboard work or export yet. ### Phase 3 · A canvas that does not fight you Build: Hit testing against shape geometry rather than bounding boxes, marquee selection, snapping to shape edges and centres with visible guides, arrow-key nudging, and text editing in place with wrapping inside the shape. Keep interaction responsive on a document with several hundred objects by redrawing only what changed. Trap: Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. Done when: Clicking the visible gap inside a rotated diamond's bounding box selects what is behind it, tested. Snapping shows a guide and can be suppressed with a held modifier, and the snap distance is in configuration. Dragging stays responsive on a document of several hundred shapes, measured against a stated frame budget. Text wraps inside the shape and the shape can grow to fit, with the choice remembered per shape. Do not build yet: No routing or export yet. ### Phase 4 · Routing that goes around things Build: Offer straight, orthogonal and curved routing per edge. Route orthogonal edges around obstacles on a grid derived from the shapes. When a user drags a waypoint, that edge becomes manually routed and stops being recomputed until they reset it. Recompute only edges whose endpoints or obstacles changed, never the whole document. Trap: Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. Done when: An orthogonal edge routes around an intervening shape rather than through it, tested with a placed obstacle. Dragging a waypoint marks the edge manual and a later shape move does not rewrite it, tested. Resetting a manual edge returns it to automatic routing. A drag on a document with many edges recomputes only the affected ones, asserted by counting recomputations. Do not build yet: No export or file format work yet. ### Phase 5 · An export that stands on its own Build: Embed every font subset and every image into the SVG as data URIs before exporting. Rasterise PNG by drawing that self-contained SVG onto a canvas. Version the document format and write an importer for the previous version. Export and import the whole document as JSON with assets. Trap: Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. Done when: An exported SVG opened on a machine without the diagram's fonts renders with the same glyphs, tested by removing the font locally. PNG export succeeds on a document containing an embedded image, tested for the absence of a security error. A document saved under the previous format version opens and is migrated, tested. A JSON export reimports into an empty browser profile with assets and history intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time collaboration, which is a different application built around a conflict-free data type rather than a feature you add to this one. - Importing other vendors' diagram formats, which is reverse engineering a moving target and never quite works. - A large template and stencil library, which is content production rather than engineering and is a real part of what the subscription buys. - Automatic layout of an entire diagram, which is a satisfying research problem and a poor use of the weekend this build has. ### README must contain - That edges reference shapes and ports rather than coordinates, and what that means for anyone extending the format - That undo is a patch log persisted with the document, and the memory budget it is held to - That exports inline every font and image, and that a PNG cannot be produced from an SVG referencing external resources - The document format version, what changed in it, and how migration runs on open - Where the data lives in the browser, how to export everything, and that clearing site data deletes it
# Agent instructions · SmartDraw indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, no server, nothing leaves the browser, IndexedDB holding a versioned document format plus periodic snapshots, Edges reference shape identifiers and named ports, never absolute points, A command log of forward and inverse patches, grouped per user gesture, SVG with every font and image embedded, and PNG rasterised from that SVG. 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 - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it.
# Build plan · SmartDraw A local diagram editor whose hard problems are an edge that references a shape rather than a pair of coordinates, an undo stack that covers every operation including the ones autosave performed, and an export that is self-contained rather than dependent on the machine that opens it. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An edge that points at a shape Moving, resizing or deleting a shape does the right thing to everything attached to it. ### Steps 1. Express ports as fractions of the shape box Absolute offsets break on the first resize. Files: `src/model/ports.ts`, `tests/geometry.test.ts` 2. Compute endpoints at render, store references The stored document should be the truth, not a cache of pixels. Files: `src/model/edges.ts` 3. Decide what a deleted shape does to its edges Both answers are fine; silently leaving arrows to nowhere is not. Files: `src/model/document.ts` ### Done when - [ ] Edges stay attached through moves and resizes - [ ] Ports scale correctly on a non-uniform resize - [ ] Deleting a shape with edges undoes as one action - [ ] No absolute endpoint coordinates are stored ### Watch out - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. ## Phase 2 · Undo that covers everything Every change is one entry on a stack that survives a reload, including changes made while you were not looking. ### Steps 1. Patches, not snapshots Snapshots are for recovery; a history of them is a memory leak. Files: `src/history/commands.ts`, `tests/history.test.ts` 2. Group a gesture into one entry Twelve undos for one drag is the same as no undo. Files: `src/history/transaction.ts` 3. Persist the stack and restore selection A reload should not be a wall you cannot step back through. Files: `src/history/persist.ts` ### Done when - [ ] History memory stays inside its budget after 200 operations - [ ] A multi-select drag undoes in one step - [ ] Undo survives a reload - [ ] Selection is restored when an action is undone ### Watch out - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. ## Phase 3 · A canvas that does not fight you Selecting, dragging, snapping and typing feel right at the size of diagram people actually draw. ### Steps 1. Hit test against real geometry Bounding boxes are why cheap editors feel cheap. Files: `src/canvas/hit.ts`, `tests/hit.test.ts` 2. Snap with visible guides and an escape hatch Snapping you cannot switch off is a trap. Files: `src/canvas/snap.ts` 3. Redraw only what changed Several hundred objects is a normal diagram, not a stress test. Files: `src/canvas/render.ts` ### Done when - [ ] Rotated shapes do not steal clicks from behind them - [ ] Snap guides appear and can be suppressed - [ ] Dragging holds the frame budget at several hundred objects - [ ] Text wraps inside shapes and growth is remembered ### Watch out - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. ## Phase 4 · Routing that goes around things Connectors take a sensible path and keep any path the user set by hand. ### Steps 1. Route on a grid derived from the shapes A general path finder is overkill; obstacle corners are enough. Files: `src/routing/orthogonal.ts`, `tests/routing.test.ts` 2. Let a hand-placed waypoint win permanently The user moved it because your router was wrong. Files: `src/routing/manual.ts` 3. Recompute only affected edges, after the gesture Routing during a drag is how editors get a reputation. Files: `src/routing/scheduler.ts` ### Done when - [ ] Orthogonal edges avoid intervening shapes - [ ] Manual waypoints survive later shape moves - [ ] Resetting returns an edge to automatic routing - [ ] Only affected edges recompute during a drag ### Watch out - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ## Not in this build - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. ## After v1, if you want it - A read-only viewer link that renders an exported document, which is most of what sharing actually means - Layers, which change selection and hit testing enough to be worth doing after the basics are solid - A text-to-diagram importer for one simple syntax, which is a better use of a model than generating shapes freehand
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Shown in the tab and in exported file names. VITE_APP_TITLE=Diagrams # Required. Snapping distance. People disagree about this, so it is configuration. VITE_SNAP_DISTANCE_PX=8 # Required. The undo stack is trimmed rather than allowed to grow without limit. VITE_HISTORY_MEMORY_BUDGET_MB=24 # Required. How often a whole-document snapshot is written for recovery. VITE_AUTOSNAPSHOT_SECONDS=120 # Required. Written into every document and checked on open. VITE_SCHEMA_VERSION=3
# SmartDraw · product brief ## Problem The visible business diagramming loop is buildable, but a credible replacement needs more than the first screen. SmartDraw 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 where arrows stay attached, one gesture is one undo that survives a reload, connectors route around shapes unless you moved them yourself, and an exported file looks the same on a machine that has none of your fonts. ## 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 - Real time collaboration. - Importing other vendors' formats. - A large template and stencil library. - Automatic whole-diagram layout. - large template libraries - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments ## Success criteria - Edges are confirmed attached after programmatic moves and resizes - A multi-select drag is confirmed to undo in one step across a reload - Rotated shapes are confirmed not to steal clicks from behind them - PNG export is confirmed to succeed on a document containing images - An exported SVG is confirmed identical without the local fonts
# Build brief · SmartDraw 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 connectors stay attached to the shapes they were drawn to to replace SmartDraw. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, no server, nothing leaves the browser · a diagram is a document and documents do not need an account - Storage: IndexedDB holding a versioned document format plus periodic snapshots · a browser database is the only durable local store and it needs a schema version from day one - Geometry: Edges reference shape identifiers and named ports, never absolute points · an edge pinned to coordinates detaches the first time somebody moves a box - History: A command log of forward and inverse patches, grouped per user gesture · one drag that moves twelve shapes must undo once, not twelve times - Export: SVG with every font and image embedded, and PNG rasterised from that SVG · an export that depends on the viewer's fonts is not an export ### Data model (create this before Phase 1) - documents(id, title, schema_version, created_at, updated_at, canvas_w, canvas_h) - nodes(id, doc_id, kind, x, y, w, h, rotation, text, style_json) - ports(node_id, name, rel_x, rel_y) · anchors expressed as fractions of the shape box so resizing moves them correctly - edges(id, doc_id, from_node, from_port, to_node, to_port, route_mode, waypoints_json, style_json) · endpoints are references, never coordinates - commands(doc_id, ordinal, label, forward_json, inverse_json, at) · the undo stack, persisted so a reload does not erase it - assets(id, doc_id, kind, mime, bytes, sha256) · fonts and images stored in the document so an export is self-contained - snapshots(doc_id, at, blob) · a periodic whole-document copy, because a command log with one corrupt entry is a lost diagram ### Phase 1 · An edge that points at a shape Build: Define shapes with named ports whose positions are fractions of the shape box. Edges store the shape identifier and the port name at each end. Recompute an edge's endpoints from the current geometry at render time. Deleting a shape either deletes its edges or leaves them dangling with a visible marker, and whichever you choose is undoable as one action. Trap: Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. Done when: Moving or resizing a shape keeps every attached edge touching it, tested by asserting endpoint positions after a programmatic move. Port positions scale correctly when a shape is resized non-uniformly, tested. Deleting a shape with three edges is one undoable action that restores all four objects. No edge in the document stores an absolute endpoint coordinate, verified in the schema. Do not build yet: No undo stack, routing, styling or export yet. ### Phase 2 · Undo that covers everything Build: Model every mutation as a command with a forward and an inverse patch. Group a whole gesture into one entry: a drag of a multiple selection, a paste, a style change across shapes. Persist the stack with the document. Restore the selection state that existed before an undone action, because undoing without restoring the selection leaves people lost. Trap: Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. Done when: Two hundred operations leave the history under a stated memory budget, measured. A drag of a ten-shape selection undoes in one step and redoes identically, tested. Reloading the page preserves the undo stack and undo still works across the reload. Undoing restores the selection that was active before the action, tested. Do not build yet: No routing, keyboard work or export yet. ### Phase 3 · A canvas that does not fight you Build: Hit testing against shape geometry rather than bounding boxes, marquee selection, snapping to shape edges and centres with visible guides, arrow-key nudging, and text editing in place with wrapping inside the shape. Keep interaction responsive on a document with several hundred objects by redrawing only what changed. Trap: Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. Done when: Clicking the visible gap inside a rotated diamond's bounding box selects what is behind it, tested. Snapping shows a guide and can be suppressed with a held modifier, and the snap distance is in configuration. Dragging stays responsive on a document of several hundred shapes, measured against a stated frame budget. Text wraps inside the shape and the shape can grow to fit, with the choice remembered per shape. Do not build yet: No routing or export yet. ### Phase 4 · Routing that goes around things Build: Offer straight, orthogonal and curved routing per edge. Route orthogonal edges around obstacles on a grid derived from the shapes. When a user drags a waypoint, that edge becomes manually routed and stops being recomputed until they reset it. Recompute only edges whose endpoints or obstacles changed, never the whole document. Trap: Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. Done when: An orthogonal edge routes around an intervening shape rather than through it, tested with a placed obstacle. Dragging a waypoint marks the edge manual and a later shape move does not rewrite it, tested. Resetting a manual edge returns it to automatic routing. A drag on a document with many edges recomputes only the affected ones, asserted by counting recomputations. Do not build yet: No export or file format work yet. ### Phase 5 · An export that stands on its own Build: Embed every font subset and every image into the SVG as data URIs before exporting. Rasterise PNG by drawing that self-contained SVG onto a canvas. Version the document format and write an importer for the previous version. Export and import the whole document as JSON with assets. Trap: Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image. Done when: An exported SVG opened on a machine without the diagram's fonts renders with the same glyphs, tested by removing the font locally. PNG export succeeds on a document containing an embedded image, tested for the absence of a security error. A document saved under the previous format version opens and is migrated, tested. A JSON export reimports into an empty browser profile with assets and history intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time collaboration, which is a different application built around a conflict-free data type rather than a feature you add to this one. - Importing other vendors' diagram formats, which is reverse engineering a moving target and never quite works. - A large template and stencil library, which is content production rather than engineering and is a real part of what the subscription buys. - Automatic layout of an entire diagram, which is a satisfying research problem and a poor use of the weekend this build has. ### README must contain - That edges reference shapes and ports rather than coordinates, and what that means for anyone extending the format - That undo is a patch log persisted with the document, and the memory budget it is held to - That exports inline every font and image, and that a PNG cannot be produced from an SVG referencing external resources - The document format version, what changed in it, and how migration runs on open - Where the data lives in the browser, how to export everything, and that clearing site data deletes it
# Architecture · SmartDraw ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, no server, nothing leaves the browser | a diagram is a document and documents do not need an account | | Storage | IndexedDB holding a versioned document format plus periodic snapshots | a browser database is the only durable local store and it needs a schema version from day one | | Geometry | Edges reference shape identifiers and named ports, never absolute points | an edge pinned to coordinates detaches the first time somebody moves a box | | History | A command log of forward and inverse patches, grouped per user gesture | one drag that moves twelve shapes must undo once, not twelve times | | Export | SVG with every font and image embedded, and PNG rasterised from that SVG | an export that depends on the viewer's fonts is not an export | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Model | Shapes, ports, edges and the format version. | Any store, provided edges keep references rather than coordinates. | | History | Command patches and gesture grouping. | Any scheme, provided it is patches rather than document snapshots. | | Canvas | Hit testing, snapping, rendering. | Any renderer, provided hit testing uses real geometry. | | Routing | Automatic paths and manual waypoints. | Any router, provided manual waypoints are never overwritten. | | Export | Inlining, subsetting, rasterising, migration. | Any pipeline, provided nothing external survives into the output. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_APP_TITLE` · required · Shown in the tab and in exported file names. - `VITE_SNAP_DISTANCE_PX` · required · Snapping distance. People disagree about this, so it is configuration. - `VITE_HISTORY_MEMORY_BUDGET_MB` · required · The undo stack is trimmed rather than allowed to grow without limit. - `VITE_AUTOSNAPSHOT_SECONDS` · required · How often a whole-document snapshot is written for recovery. - `VITE_SCHEMA_VERSION` · required · Written into every document and checked on open. ## 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 · SmartDraw product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, no server, nothing leaves the browser, IndexedDB holding a versioned document format plus periodic snapshots, Edges reference shape identifiers and named ports, never absolute points, A command log of forward and inverse patches, grouped per user gesture, SVG with every font and image embedded, and PNG rasterised from that SVG. - 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 - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. - Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image.
# Delivery milestones · SmartDraw Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An edge that points at a shape Moving, resizing or deleting a shape does the right thing to everything attached to it. ### Steps 1. Express ports as fractions of the shape box Absolute offsets break on the first resize. Files: `src/model/ports.ts`, `tests/geometry.test.ts` 2. Compute endpoints at render, store references The stored document should be the truth, not a cache of pixels. Files: `src/model/edges.ts` 3. Decide what a deleted shape does to its edges Both answers are fine; silently leaving arrows to nowhere is not. Files: `src/model/document.ts` ### Done when - [ ] Edges stay attached through moves and resizes - [ ] Ports scale correctly on a non-uniform resize - [ ] Deleting a shape with edges undoes as one action - [ ] No absolute endpoint coordinates are stored ### Watch out - Storing an edge as two points because it renders faster means every arrow detaches as soon as a box is dragged, and the user's repair work is what they will remember about your editor. Store references and ports, compute the points on render, and test by moving a shape a thousand pixels and asserting the edge still touches it. ## M2 · Undo that covers everything Every change is one entry on a stack that survives a reload, including changes made while you were not looking. ### Steps 1. Patches, not snapshots Snapshots are for recovery; a history of them is a memory leak. Files: `src/history/commands.ts`, `tests/history.test.ts` 2. Group a gesture into one entry Twelve undos for one drag is the same as no undo. Files: `src/history/transaction.ts` 3. Persist the stack and restore selection A reload should not be a wall you cannot step back through. Files: `src/history/persist.ts` ### Done when - [ ] History memory stays inside its budget after 200 operations - [ ] A multi-select drag undoes in one step - [ ] Undo survives a reload - [ ] Selection is restored when an action is undone ### Watch out - Implementing undo as a stack of whole-document snapshots works until a diagram has a few hundred objects and an embedded image, at which point every drag allocates megabytes and the editor stutters exactly when the user is being precise. Store patches, keep periodic snapshots for recovery only, and measure the memory the stack uses after two hundred operations. ## M3 · A canvas that does not fight you Selecting, dragging, snapping and typing feel right at the size of diagram people actually draw. ### Steps 1. Hit test against real geometry Bounding boxes are why cheap editors feel cheap. Files: `src/canvas/hit.ts`, `tests/hit.test.ts` 2. Snap with visible guides and an escape hatch Snapping you cannot switch off is a trap. Files: `src/canvas/snap.ts` 3. Redraw only what changed Several hundred objects is a normal diagram, not a stress test. Files: `src/canvas/render.ts` ### Done when - [ ] Rotated shapes do not steal clicks from behind them - [ ] Snap guides appear and can be suppressed - [ ] Dragging holds the frame budget at several hundred objects - [ ] Text wraps inside shapes and growth is remembered ### Watch out - Hit testing on bounding boxes makes every diamond and circle grab clicks from the shape behind it, and the user cannot select the thing under the corner of a rotated box no matter how carefully they aim. Test against the actual geometry, and verify with a rotated shape overlapping another. ## M4 · Routing that goes around things Connectors take a sensible path and keep any path the user set by hand. ### Steps 1. Route on a grid derived from the shapes A general path finder is overkill; obstacle corners are enough. Files: `src/routing/orthogonal.ts`, `tests/routing.test.ts` 2. Let a hand-placed waypoint win permanently The user moved it because your router was wrong. Files: `src/routing/manual.ts` 3. Recompute only affected edges, after the gesture Routing during a drag is how editors get a reputation. Files: `src/routing/scheduler.ts` ### Done when - [ ] Orthogonal edges avoid intervening shapes - [ ] Manual waypoints survive later shape moves - [ ] Resetting returns an edge to automatic routing - [ ] Only affected edges recompute during a drag ### Watch out - Recomputing every route on every frame of a drag turns a smooth gesture into a slideshow on a medium diagram, and recomputing all of them on load makes opening a document feel broken. Recompute only affected edges, and do it after the drag settles rather than during it. ## M5 · An export that stands on its own (production only) The exported file looks the same on a machine that has none of your fonts, and the document format is portable. ### Steps 1. Inline fonts and images before rasterising An external reference is what makes the canvas refuse to give you the image. Files: `src/export/inline.ts`, `tests/export.test.ts` 2. Subset the font rather than embedding all of it A whole font is megabytes and a diagram uses forty glyphs. Files: `src/export/fonts.ts` 3. Version the format and write the migration The second version is the one that loses people's files. Files: `src/model/migrate.ts` ### Done when - [ ] SVG renders identically without the local fonts - [ ] PNG export succeeds on a document with images - [ ] A previous-version document migrates on open - [ ] JSON export reimports into an empty profile ### Watch out - Rasterising an SVG that still references an image or a font by URL taints the canvas, and the browser then throws a security error from the very call you use to produce the PNG, so your export button fails on exactly the diagrams that have pictures in them. Inline every external resource as a data URI first, and test the PNG path with a document containing an embedded image.
# Operations · SmartDraw ## Backup Browser storage is deleted by the browser without asking, so the backup story is the export: a scheduled reminder to export the whole document set as JSON, and a note in the interface saying when the last export happened. ## Restore Import the JSON into a fresh profile and confirm document count, asset checksums and that a sample diagram exports to an identical SVG. If the assets did not come back, exports will start depending on local fonts again and you will not notice immediately. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor, the measurements live in the suite and the interface: history memory, frame time during a drag on a large document, export failure rate, and the age of the last full export. ## Incident checklist The two incidents are a lost document and an export that silently changed appearance. The first is answered by snapshots plus reminded exports, the second by the test that renders without the local fonts installed. 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 - [ ] Edges are confirmed attached after programmatic moves and resizes - [ ] A multi-select drag is confirmed to undo in one step across a reload - [ ] Rotated shapes are confirmed not to steal clicks from behind them - [ ] PNG export is confirmed to succeed on a document containing images - [ ] An exported SVG is confirmed identical without the local fonts ## Launch constraint Do not market omitted SmartDraw 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. Shown in the tab and in exported file names. VITE_APP_TITLE=Diagrams # Required. Snapping distance. People disagree about this, so it is configuration. VITE_SNAP_DISTANCE_PX=8 # Required. The undo stack is trimmed rather than allowed to grow without limit. VITE_HISTORY_MEMORY_BUDGET_MB=24 # Required. How often a whole-document snapshot is written for recovery. VITE_AUTOSNAPSHOT_SECONDS=120 # Required. Written into every document and checked on open. VITE_SCHEMA_VERSION=3
$ choose a build depth, inspect the files, then open the complete pack in your agent
SmartDraw: 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
SmartDraw pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| individual | — | $9.95/user | 1 user; full diagramming/floor-planning product; numeric document/storage caps not published |
| team | — | $8.95/user | 3-user minimum; 1 shared team folder; admin controls, document retention and premium support |
| enterprise | custom | — | Team features plus SSO, multiple shared folders, custom company theme and dedicated success manager |
free tierno free tier (free trial offered; duration not published)
billingannual only; no monthly subscription
hidden costsAnnual payment is required upfront. Team has a 3-seat minimum. $9.95/$8.95 are introductory new-customer rates; the page also shows $10.95 standard pricing, so renewal cost may be higher, but the renewal rule is not stated.
verified 2026-08-12 · source ↗
Vibecode SmartDraw
Kinda. The core of SmartDraw 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 SmartDraw cost?
SmartDraw'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 SmartDraw?
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 SmartDraw?
Yes: diagrams.net (The closest broad replacement: flowcharts, network diagrams, floor plans, org charts, engineering shapes, templates, and local files without a subscription.) yEd (Automatic layout is the star here, especially for org charts, networks, processes, and dependency graphs; floor-plan tooling is much thinner.) The prompt is for when you want it exactly your way.