Vibecode Sketch
track this build5 phases, 15 steps, beginner friendly0%The visible interface design loop is buildable, but a credible replacement needs more than the first screen. Sketch earns its keep through editor polish, assets, algorithms, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of Sketch. 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 ===== # Sketch · indie build A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. 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 | | --- | --- | --- | | Shell | Tauri with a TypeScript front end | a real desktop application with filesystem access and a native file dialog | | Rendering | SVG in the DOM for the canvas | vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary | | Geometry | A boolean path library operating on flattened paths | union, subtract and intersect on Bezier paths is genuinely hard and not worth writing | | Document | A JSON document format, one file per document, human diffable | design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats | | Export | resvg for rasterising, with scale factors applied to the source geometry | rasterising once per scale from the vector source, never upscaling a bitmap | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the front end and the tooling. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The Rust toolchain** · free - Why: Tauri compiles the desktop shell and hosts the rasteriser. - Get it: rustup from rustup.rs, plus the platform packages Tauri lists. - Verify: A Tauri hello world builds and runs - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Export can embed or outline fonts, which is redistribution and is not permitted by every licence. - Get it: Use openly licensed families and keep the licence files. Check the embedding clause before Phase 5 if you use a commercial face. - Verify: Each bundled font's licence permits embedding, or export outlines text - [ ] **Two real interface designs to rebuild** · free - Why: Boolean operations, components and auto-layout only reveal their gaps against real work. A test file of three rectangles will not. - Get it: Rebuild two screens you have designed before, or two from an open design system. - Verify: Both are rebuilt and the friction points are noted - [ ] **A decision about colour space** · free - Why: Colour management is explicitly out of scope, so the document must state which space it works in or exports will not match expectations. - Get it: Pick one, usually standard RGB, and state it in the README and in the interface. - Verify: The colour space is stated in the README ## Quick start ```sh npm create tauri-app@latest ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. - color-management edge cases - cloud collaboration and mobile apps - pixel-perfect professional tooling - large template and asset libraries If one of those is essential to you, that is the reason to keep paying for Sketch, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Sketch 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 local vector interface design editor with components, boolean operations and export at real scale factors to replace Sketch. 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) - Shell: Tauri with a TypeScript front end · a real desktop application with filesystem access and a native file dialog - Rendering: SVG in the DOM for the canvas · vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary - Geometry: A boolean path library operating on flattened paths · union, subtract and intersect on Bezier paths is genuinely hard and not worth writing - Document: A JSON document format, one file per document, human diffable · design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats - Export: resvg for rasterising, with scale factors applied to the source geometry · rasterising once per scale from the vector source, never upscaling a bitmap ### Data model (create this before Phase 1) - documents(id, name, path, updated_at) - pages(id, doc_id, name, order) - layers(id, page_id, parent_id, kind, name, x, y, w, h, rotation, opacity, visible, locked, z) · kind is frame, rect, ellipse, path, text, image, component or instance - styles(id, doc_id, kind, name, value_json) · kind is fill, text or effect, referenced by layers rather than copied - components(id, doc_id, root_layer_id, name), instances(layer_id, component_id, overrides_json) - An instance stores only its overrides. Everything else resolves from the component, which is what makes changing the component change every instance. ### Phase 1 · Canvas, shapes and precise geometry Build: An SVG canvas with pan and zoom through the viewBox. Rectangles, ellipses and paths with an inspector showing exact position and size. Snapping to other objects and to a pixel grid, with the snap visible while dragging. Trap: Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. Done when: A shape moved and returned reports its original coordinates exactly. A shape rotated ninety degrees four times returns to its starting geometry without drift. Snapping aligns edges to other objects and the alignment guide is visible during the drag. Do not build yet: No boolean operations, no components, no export. ### Phase 2 · Boolean operations and path editing Build: Union, subtract, intersect and difference over paths using a boolean library. A path editor for moving points and handles. Keep the operands so a boolean result can be un-combined, rather than destroying the inputs. Trap: Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. Done when: Union of two overlapping rectangles produces one path with the correct outline. Subtracting a circle from a rectangle leaves a hole rather than a separate shape. Two rectangles sharing exactly one edge union into a single shape without artefacts. Un-combining restores both original shapes. Do not build yet: No components or auto-layout yet. ### Phase 3 · Components and overrides Build: Turn a selection into a component. Place instances that resolve their content from the component. Allow overrides of text, fill and visibility per instance, stored as a sparse set rather than a copy. Editing the component updates every instance that has not overridden the changed property. Trap: Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. Done when: Editing a component's fill changes every instance that has not overridden the fill, and leaves overridden ones alone. An instance's override survives an unrelated component change. Deleting a component leaves instances in a clearly broken state that names the missing component rather than silently rendering nothing. Do not build yet: No auto-layout or export yet. ### Phase 4 · Auto-layout and text styles Build: Stack layout on frames: direction, spacing, padding and alignment, with children reflowing when content changes size. Text styles and colour variables referenced by name so a change updates everywhere it is used. Trap: Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: Adding text to a stacked frame grows the frame rather than overflowing. Changing a text style's size reflows every frame using it. A colour variable change updates every layer referencing it and leaves literal colours alone. Nesting a stack inside a stack reflows both correctly. Do not build yet: No export yet. ### Phase 5 · Export at real scale factors Build: Export presets per layer or frame: format, scale factor and naming pattern. Rasterise from the vector source at each scale rather than scaling a rendered bitmap. Batch export everything marked exportable in one action. Trap: Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. Done when: A two times export has exactly double the pixel dimensions of the one times, and is visibly sharper rather than merely larger. A batch export produces every marked asset with the naming pattern applied. An SVG export opens correctly in another application with text either embedded or outlined. Do not build yet: Sharing and handoff come next. ### Phase 6 · Handoff, versioning and file safety Build: A read-only inspection view exposing measurements, colours and text styles. Save atomically with a crash-safe write. Because the document format is diffable JSON, make version control the versioning story rather than building one. Trap: Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. Done when: A crash simulated during a save leaves either the previous document or the new one, never a truncated file. The handoff view shows measurements and colour values without allowing edits. A document committed to version control produces a readable diff when a single layer moves. Do not build yet: Nothing after this. ### Out of scope (and why) - Colour management across profiles. Getting colour identical between a design tool, a browser and a printer is a deep problem and this build works in one colour space and says so. - Real-time collaborative editing and the cloud workspace, which is where the industry moved and is a substantially larger project. - Prototyping with interactions and transitions, which is a second product bolted onto the first. - Plugin ecosystems and the third party tooling that has grown around the established formats. ### README must contain - That geometry is stored at full precision and rounded only for display and export - That instances store overrides rather than copies, and what that means when a component changes - That text measurement waits for real font loading - That every scale factor is rasterised from the vector source, never upscaled - That the document format is diffable JSON and version control is the versioning story ===== AGENTS.md ===== # Agent instructions · Sketch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri with a TypeScript front end, SVG in the DOM for the canvas, A boolean path library operating on flattened paths, A JSON document format, one file per document, human diffable, resvg for rasterising, with scale factors applied to the source geometry. 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 - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ===== BUILD_PLAN.md ===== # Build plan · Sketch A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Canvas, shapes and precise geometry You can draw and position shapes exactly, and the numbers in the inspector are the truth. ### Steps 1. Set up the SVG canvas and viewBox transform Pan and zoom through the viewBox so what you see is the geometry you will export. Files: `src/canvas.ts` ```sh npm create tauri-app@latest ``` 2. Keep full precision in the document Store floating point, display rounded. Rounding on write is what causes drift. Files: `src/document.ts` 3. Implement snapping with visible guides Snap to object edges, centres and a grid. Show the guide, because invisible snapping feels like the tool fighting you. Files: `src/snap.ts` ### Done when - [ ] A shape moved and returned reports its original coordinates - [ ] Four ninety degree rotations return the original geometry - [ ] Snap guides are visible during a drag - [ ] The inspector values match the document values ### Watch out - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. ## Phase 2 · Boolean operations and path editing You can combine shapes into new ones and edit the resulting paths. ### Steps 1. Add the boolean library Flatten curves to the library's expected form, operate, and convert back. The conversion at each boundary is where precision is lost, so do it once. Files: `src/boolean.ts` 2. Keep operands for non-destructive editing A boolean result references its inputs. Un-combining is then trivial and users expect it. Files: `src/boolean.ts` 3. Test the degenerate cases Shared edges, tangent overlaps, fully contained shapes and zero-area results. These are the cases that appear in real work. Files: `src/boolean.test.ts` ### Done when - [ ] Union of overlapping rectangles produces the correct outline - [ ] Subtraction leaves a hole rather than a separate shape - [ ] Rectangles sharing one edge union cleanly - [ ] Un-combining restores the original shapes ### Watch out - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. ## Phase 3 · Components and overrides Changing a component changes every instance, except where an instance deliberately differs. ### Steps 1. Implement components and sparse overrides An instance is a component reference plus an override map. Nothing else. Files: `src/components.ts` 2. Resolve at render time Instances resolve from the component on every render, which is what makes propagation automatic rather than a synchronisation step. Files: `src/resolve.ts` 3. Handle a missing component visibly Name what is missing. Silence here produces documents with invisible holes. Files: `src/resolve.ts` ### Done when - [ ] A component change propagates to non-overridden instances - [ ] An overridden property is not affected by the component change - [ ] Overrides survive unrelated component edits - [ ] A deleted component leaves a named broken state ### Watch out - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. ## Phase 4 · Auto-layout and text styles Content that grows reflows its container instead of overflowing it. ### Steps 1. Implement stack layout Direction, gap, padding and alignment, computed top down with children measured first. Files: `src/layout.ts` 2. Measure text with the real font Wait for font loading and re-measure on load. This is the single most common source of layout that is subtly wrong. Files: `src/text.ts` 3. Add styles and variables as references Layers reference a style by identifier. A literal value is a deliberate choice, not the default. Files: `src/styles.ts` ### Done when - [ ] Growing text grows its stacked frame - [ ] A text style change reflows every frame using it - [ ] A colour variable change updates references and leaves literals alone - [ ] Nested stacks reflow correctly ### Watch out - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 5 · Export at real scale factors Assets come out at exactly the sizes platforms require, from the vector source every time. ### Steps 1. Define export presets per layer Format, scale and a naming pattern. Multiple presets per layer, because platforms want several. Files: `src/export.ts` 2. Rasterise from geometry per scale Render the vector at each scale factor. Never upscale a bitmap. Files: `src/raster.ts` 3. Verify sharpness, not just size Compare a two times export against an upscaled one times. The difference should be obvious, and if it is not, you are upscaling. ### Done when - [ ] A two times export has double the dimensions and more detail - [ ] Batch export applies the naming pattern to every marked asset - [ ] SVG export opens correctly elsewhere - [ ] Exporting twice produces identical files ### Watch out - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ## Not in this build - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. ## After v1, if you want it - Component variants, which are the natural next step once overrides work - A command line export so continuous integration can produce assets - Importing an existing design system's tokens as styles and variables ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Default location for documents. Version control friendly because the format is JSON. DOCUMENTS_DIR=~/Documents/Designs # Required. Where batch exports are written. EXPORT_DIR=~/Documents/Designs/exports # Required. How close a drag must come before snapping engages, in screen pixels rather than document units. SNAP_TOLERANCE_PX=6 # Required. How often to save. Saves are atomic, so a shorter interval is safe. AUTOSAVE_INTERVAL_MS=30000 # Required. The single colour space this application works in. Stated rather than managed. COLOR_SPACE=srgb
You are building a lean indie version of Sketch. 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 ===== # Sketch · indie build A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. 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 | | --- | --- | --- | | Shell | Tauri with a TypeScript front end | a real desktop application with filesystem access and a native file dialog | | Rendering | SVG in the DOM for the canvas | vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary | | Geometry | A boolean path library operating on flattened paths | union, subtract and intersect on Bezier paths is genuinely hard and not worth writing | | Document | A JSON document format, one file per document, human diffable | design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats | | Export | resvg for rasterising, with scale factors applied to the source geometry | rasterising once per scale from the vector source, never upscaling a bitmap | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the front end and the tooling. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The Rust toolchain** · free - Why: Tauri compiles the desktop shell and hosts the rasteriser. - Get it: rustup from rustup.rs, plus the platform packages Tauri lists. - Verify: A Tauri hello world builds and runs - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Export can embed or outline fonts, which is redistribution and is not permitted by every licence. - Get it: Use openly licensed families and keep the licence files. Check the embedding clause before Phase 5 if you use a commercial face. - Verify: Each bundled font's licence permits embedding, or export outlines text - [ ] **Two real interface designs to rebuild** · free - Why: Boolean operations, components and auto-layout only reveal their gaps against real work. A test file of three rectangles will not. - Get it: Rebuild two screens you have designed before, or two from an open design system. - Verify: Both are rebuilt and the friction points are noted - [ ] **A decision about colour space** · free - Why: Colour management is explicitly out of scope, so the document must state which space it works in or exports will not match expectations. - Get it: Pick one, usually standard RGB, and state it in the README and in the interface. - Verify: The colour space is stated in the README ## Quick start ```sh npm create tauri-app@latest ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. - color-management edge cases - cloud collaboration and mobile apps - pixel-perfect professional tooling - large template and asset libraries If one of those is essential to you, that is the reason to keep paying for Sketch, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Sketch 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 local vector interface design editor with components, boolean operations and export at real scale factors to replace Sketch. 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) - Shell: Tauri with a TypeScript front end · a real desktop application with filesystem access and a native file dialog - Rendering: SVG in the DOM for the canvas · vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary - Geometry: A boolean path library operating on flattened paths · union, subtract and intersect on Bezier paths is genuinely hard and not worth writing - Document: A JSON document format, one file per document, human diffable · design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats - Export: resvg for rasterising, with scale factors applied to the source geometry · rasterising once per scale from the vector source, never upscaling a bitmap ### Data model (create this before Phase 1) - documents(id, name, path, updated_at) - pages(id, doc_id, name, order) - layers(id, page_id, parent_id, kind, name, x, y, w, h, rotation, opacity, visible, locked, z) · kind is frame, rect, ellipse, path, text, image, component or instance - styles(id, doc_id, kind, name, value_json) · kind is fill, text or effect, referenced by layers rather than copied - components(id, doc_id, root_layer_id, name), instances(layer_id, component_id, overrides_json) - An instance stores only its overrides. Everything else resolves from the component, which is what makes changing the component change every instance. ### Phase 1 · Canvas, shapes and precise geometry Build: An SVG canvas with pan and zoom through the viewBox. Rectangles, ellipses and paths with an inspector showing exact position and size. Snapping to other objects and to a pixel grid, with the snap visible while dragging. Trap: Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. Done when: A shape moved and returned reports its original coordinates exactly. A shape rotated ninety degrees four times returns to its starting geometry without drift. Snapping aligns edges to other objects and the alignment guide is visible during the drag. Do not build yet: No boolean operations, no components, no export. ### Phase 2 · Boolean operations and path editing Build: Union, subtract, intersect and difference over paths using a boolean library. A path editor for moving points and handles. Keep the operands so a boolean result can be un-combined, rather than destroying the inputs. Trap: Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. Done when: Union of two overlapping rectangles produces one path with the correct outline. Subtracting a circle from a rectangle leaves a hole rather than a separate shape. Two rectangles sharing exactly one edge union into a single shape without artefacts. Un-combining restores both original shapes. Do not build yet: No components or auto-layout yet. ### Phase 3 · Components and overrides Build: Turn a selection into a component. Place instances that resolve their content from the component. Allow overrides of text, fill and visibility per instance, stored as a sparse set rather than a copy. Editing the component updates every instance that has not overridden the changed property. Trap: Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. Done when: Editing a component's fill changes every instance that has not overridden the fill, and leaves overridden ones alone. An instance's override survives an unrelated component change. Deleting a component leaves instances in a clearly broken state that names the missing component rather than silently rendering nothing. Do not build yet: No auto-layout or export yet. ### Phase 4 · Auto-layout and text styles Build: Stack layout on frames: direction, spacing, padding and alignment, with children reflowing when content changes size. Text styles and colour variables referenced by name so a change updates everywhere it is used. Trap: Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: Adding text to a stacked frame grows the frame rather than overflowing. Changing a text style's size reflows every frame using it. A colour variable change updates every layer referencing it and leaves literal colours alone. Nesting a stack inside a stack reflows both correctly. Do not build yet: No export yet. ### Phase 5 · Export at real scale factors Build: Export presets per layer or frame: format, scale factor and naming pattern. Rasterise from the vector source at each scale rather than scaling a rendered bitmap. Batch export everything marked exportable in one action. Trap: Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. Done when: A two times export has exactly double the pixel dimensions of the one times, and is visibly sharper rather than merely larger. A batch export produces every marked asset with the naming pattern applied. An SVG export opens correctly in another application with text either embedded or outlined. Do not build yet: Sharing and handoff come next. ### Phase 6 · Handoff, versioning and file safety Build: A read-only inspection view exposing measurements, colours and text styles. Save atomically with a crash-safe write. Because the document format is diffable JSON, make version control the versioning story rather than building one. Trap: Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. Done when: A crash simulated during a save leaves either the previous document or the new one, never a truncated file. The handoff view shows measurements and colour values without allowing edits. A document committed to version control produces a readable diff when a single layer moves. Do not build yet: Nothing after this. ### Out of scope (and why) - Colour management across profiles. Getting colour identical between a design tool, a browser and a printer is a deep problem and this build works in one colour space and says so. - Real-time collaborative editing and the cloud workspace, which is where the industry moved and is a substantially larger project. - Prototyping with interactions and transitions, which is a second product bolted onto the first. - Plugin ecosystems and the third party tooling that has grown around the established formats. ### README must contain - That geometry is stored at full precision and rounded only for display and export - That instances store overrides rather than copies, and what that means when a component changes - That text measurement waits for real font loading - That every scale factor is rasterised from the vector source, never upscaled - That the document format is diffable JSON and version control is the versioning story ===== AGENTS.md ===== # Agent instructions · Sketch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri with a TypeScript front end, SVG in the DOM for the canvas, A boolean path library operating on flattened paths, A JSON document format, one file per document, human diffable, resvg for rasterising, with scale factors applied to the source geometry. 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 - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ===== BUILD_PLAN.md ===== # Build plan · Sketch A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Canvas, shapes and precise geometry You can draw and position shapes exactly, and the numbers in the inspector are the truth. ### Steps 1. Set up the SVG canvas and viewBox transform Pan and zoom through the viewBox so what you see is the geometry you will export. Files: `src/canvas.ts` ```sh npm create tauri-app@latest ``` 2. Keep full precision in the document Store floating point, display rounded. Rounding on write is what causes drift. Files: `src/document.ts` 3. Implement snapping with visible guides Snap to object edges, centres and a grid. Show the guide, because invisible snapping feels like the tool fighting you. Files: `src/snap.ts` ### Done when - [ ] A shape moved and returned reports its original coordinates - [ ] Four ninety degree rotations return the original geometry - [ ] Snap guides are visible during a drag - [ ] The inspector values match the document values ### Watch out - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. ## Phase 2 · Boolean operations and path editing You can combine shapes into new ones and edit the resulting paths. ### Steps 1. Add the boolean library Flatten curves to the library's expected form, operate, and convert back. The conversion at each boundary is where precision is lost, so do it once. Files: `src/boolean.ts` 2. Keep operands for non-destructive editing A boolean result references its inputs. Un-combining is then trivial and users expect it. Files: `src/boolean.ts` 3. Test the degenerate cases Shared edges, tangent overlaps, fully contained shapes and zero-area results. These are the cases that appear in real work. Files: `src/boolean.test.ts` ### Done when - [ ] Union of overlapping rectangles produces the correct outline - [ ] Subtraction leaves a hole rather than a separate shape - [ ] Rectangles sharing one edge union cleanly - [ ] Un-combining restores the original shapes ### Watch out - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. ## Phase 3 · Components and overrides Changing a component changes every instance, except where an instance deliberately differs. ### Steps 1. Implement components and sparse overrides An instance is a component reference plus an override map. Nothing else. Files: `src/components.ts` 2. Resolve at render time Instances resolve from the component on every render, which is what makes propagation automatic rather than a synchronisation step. Files: `src/resolve.ts` 3. Handle a missing component visibly Name what is missing. Silence here produces documents with invisible holes. Files: `src/resolve.ts` ### Done when - [ ] A component change propagates to non-overridden instances - [ ] An overridden property is not affected by the component change - [ ] Overrides survive unrelated component edits - [ ] A deleted component leaves a named broken state ### Watch out - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. ## Phase 4 · Auto-layout and text styles Content that grows reflows its container instead of overflowing it. ### Steps 1. Implement stack layout Direction, gap, padding and alignment, computed top down with children measured first. Files: `src/layout.ts` 2. Measure text with the real font Wait for font loading and re-measure on load. This is the single most common source of layout that is subtly wrong. Files: `src/text.ts` 3. Add styles and variables as references Layers reference a style by identifier. A literal value is a deliberate choice, not the default. Files: `src/styles.ts` ### Done when - [ ] Growing text grows its stacked frame - [ ] A text style change reflows every frame using it - [ ] A colour variable change updates references and leaves literals alone - [ ] Nested stacks reflow correctly ### Watch out - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 5 · Export at real scale factors Assets come out at exactly the sizes platforms require, from the vector source every time. ### Steps 1. Define export presets per layer Format, scale and a naming pattern. Multiple presets per layer, because platforms want several. Files: `src/export.ts` 2. Rasterise from geometry per scale Render the vector at each scale factor. Never upscale a bitmap. Files: `src/raster.ts` 3. Verify sharpness, not just size Compare a two times export against an upscaled one times. The difference should be obvious, and if it is not, you are upscaling. ### Done when - [ ] A two times export has double the dimensions and more detail - [ ] Batch export applies the naming pattern to every marked asset - [ ] SVG export opens correctly elsewhere - [ ] Exporting twice produces identical files ### Watch out - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ## Not in this build - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. ## After v1, if you want it - Component variants, which are the natural next step once overrides work - A command line export so continuous integration can produce assets - Importing an existing design system's tokens as styles and variables ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Default location for documents. Version control friendly because the format is JSON. DOCUMENTS_DIR=~/Documents/Designs # Required. Where batch exports are written. EXPORT_DIR=~/Documents/Designs/exports # Required. How close a drag must come before snapping engages, in screen pixels rather than document units. SNAP_TOLERANCE_PX=6 # Required. How often to save. Saves are atomic, so a shorter interval is safe. AUTOSAVE_INTERVAL_MS=30000 # Required. The single colour space this application works in. Stated rather than managed. COLOR_SPACE=srgb
You are building a production product version of Sketch. 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 ===== # Sketch · product brief ## Problem The visible interface design loop is buildable, but a credible replacement needs more than the first screen. Sketch earns its keep through editor polish, assets, algorithms, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A vector editor with precise geometry that does not drift, boolean operations that survive shared edges, components whose overrides are explicit, layout that reflows with real font metrics, and exports rasterised from vectors at every scale. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - sample user-owned assets ## Explicit non-goals for v1 - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. - color-management edge cases - cloud collaboration and mobile apps - pixel-perfect professional tooling - large template and asset libraries ## Success criteria - Four rotations return the original geometry without drift - The boolean degenerate case tests pass, including shared edges - A component change is confirmed to propagate to non-overridden instances only - A two times export is confirmed sharper than an upscaled one times - A killed process during save leaves a complete file ===== BRIEF.md ===== # Build brief · Sketch 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 local vector interface design editor with components, boolean operations and export at real scale factors to replace Sketch. 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) - Shell: Tauri with a TypeScript front end · a real desktop application with filesystem access and a native file dialog - Rendering: SVG in the DOM for the canvas · vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary - Geometry: A boolean path library operating on flattened paths · union, subtract and intersect on Bezier paths is genuinely hard and not worth writing - Document: A JSON document format, one file per document, human diffable · design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats - Export: resvg for rasterising, with scale factors applied to the source geometry · rasterising once per scale from the vector source, never upscaling a bitmap ### Data model (create this before Phase 1) - documents(id, name, path, updated_at) - pages(id, doc_id, name, order) - layers(id, page_id, parent_id, kind, name, x, y, w, h, rotation, opacity, visible, locked, z) · kind is frame, rect, ellipse, path, text, image, component or instance - styles(id, doc_id, kind, name, value_json) · kind is fill, text or effect, referenced by layers rather than copied - components(id, doc_id, root_layer_id, name), instances(layer_id, component_id, overrides_json) - An instance stores only its overrides. Everything else resolves from the component, which is what makes changing the component change every instance. ### Phase 1 · Canvas, shapes and precise geometry Build: An SVG canvas with pan and zoom through the viewBox. Rectangles, ellipses and paths with an inspector showing exact position and size. Snapping to other objects and to a pixel grid, with the snap visible while dragging. Trap: Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. Done when: A shape moved and returned reports its original coordinates exactly. A shape rotated ninety degrees four times returns to its starting geometry without drift. Snapping aligns edges to other objects and the alignment guide is visible during the drag. Do not build yet: No boolean operations, no components, no export. ### Phase 2 · Boolean operations and path editing Build: Union, subtract, intersect and difference over paths using a boolean library. A path editor for moving points and handles. Keep the operands so a boolean result can be un-combined, rather than destroying the inputs. Trap: Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. Done when: Union of two overlapping rectangles produces one path with the correct outline. Subtracting a circle from a rectangle leaves a hole rather than a separate shape. Two rectangles sharing exactly one edge union into a single shape without artefacts. Un-combining restores both original shapes. Do not build yet: No components or auto-layout yet. ### Phase 3 · Components and overrides Build: Turn a selection into a component. Place instances that resolve their content from the component. Allow overrides of text, fill and visibility per instance, stored as a sparse set rather than a copy. Editing the component updates every instance that has not overridden the changed property. Trap: Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. Done when: Editing a component's fill changes every instance that has not overridden the fill, and leaves overridden ones alone. An instance's override survives an unrelated component change. Deleting a component leaves instances in a clearly broken state that names the missing component rather than silently rendering nothing. Do not build yet: No auto-layout or export yet. ### Phase 4 · Auto-layout and text styles Build: Stack layout on frames: direction, spacing, padding and alignment, with children reflowing when content changes size. Text styles and colour variables referenced by name so a change updates everywhere it is used. Trap: Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: Adding text to a stacked frame grows the frame rather than overflowing. Changing a text style's size reflows every frame using it. A colour variable change updates every layer referencing it and leaves literal colours alone. Nesting a stack inside a stack reflows both correctly. Do not build yet: No export yet. ### Phase 5 · Export at real scale factors Build: Export presets per layer or frame: format, scale factor and naming pattern. Rasterise from the vector source at each scale rather than scaling a rendered bitmap. Batch export everything marked exportable in one action. Trap: Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. Done when: A two times export has exactly double the pixel dimensions of the one times, and is visibly sharper rather than merely larger. A batch export produces every marked asset with the naming pattern applied. An SVG export opens correctly in another application with text either embedded or outlined. Do not build yet: Sharing and handoff come next. ### Phase 6 · Handoff, versioning and file safety Build: A read-only inspection view exposing measurements, colours and text styles. Save atomically with a crash-safe write. Because the document format is diffable JSON, make version control the versioning story rather than building one. Trap: Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. Done when: A crash simulated during a save leaves either the previous document or the new one, never a truncated file. The handoff view shows measurements and colour values without allowing edits. A document committed to version control produces a readable diff when a single layer moves. Do not build yet: Nothing after this. ### Out of scope (and why) - Colour management across profiles. Getting colour identical between a design tool, a browser and a printer is a deep problem and this build works in one colour space and says so. - Real-time collaborative editing and the cloud workspace, which is where the industry moved and is a substantially larger project. - Prototyping with interactions and transitions, which is a second product bolted onto the first. - Plugin ecosystems and the third party tooling that has grown around the established formats. ### README must contain - That geometry is stored at full precision and rounded only for display and export - That instances store overrides rather than copies, and what that means when a component changes - That text measurement waits for real font loading - That every scale factor is rasterised from the vector source, never upscaled - That the document format is diffable JSON and version control is the versioning story ===== ARCHITECTURE.md ===== # Architecture · Sketch ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri with a TypeScript front end | a real desktop application with filesystem access and a native file dialog | | Rendering | SVG in the DOM for the canvas | vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary | | Geometry | A boolean path library operating on flattened paths | union, subtract and intersect on Bezier paths is genuinely hard and not worth writing | | Document | A JSON document format, one file per document, human diffable | design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats | | Export | resvg for rasterising, with scale factors applied to the source geometry | rasterising once per scale from the vector source, never upscaling a bitmap | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | Layers, pages, styles and serialisation. | JSON with stable ordering is a deliberate choice for diffability. A binary format would be faster and would give that up. | | Geometry | Transforms, snapping and precision. | The full-precision rule must survive any change. Rounding on write is the failure mode. | | Boolean | Path combination. | Library choice is replaceable; the degenerate case tests must come with it, because every library fails differently. | | Components | Definition, instances and override resolution. | Sparse overrides resolved at render is the design. A copy-based approach is simpler and loses the propagation guarantee. | | Export | Presets and rasterisation per scale. | Any rasteriser. Rendering from geometry at each scale is the correctness property. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DOCUMENTS_DIR` · required · Default location for documents. Version control friendly because the format is JSON. - `EXPORT_DIR` · required · Where batch exports are written. - `SNAP_TOLERANCE_PX` · required · How close a drag must come before snapping engages, in screen pixels rather than document units. - `AUTOSAVE_INTERVAL_MS` · required · How often to save. Saves are atomic, so a shorter interval is safe. - `COLOR_SPACE` · required · The single colour space this application works in. Stated rather than managed. ## 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 · Sketch product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri with a TypeScript front end, SVG in the DOM for the canvas, A boolean path library operating on flattened paths, A JSON document format, one file per document, human diffable, resvg for rasterising, with scale factors applied to the source geometry. - 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 - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. - Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. ===== MILESTONES.md ===== # Delivery milestones · Sketch Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Canvas, shapes and precise geometry You can draw and position shapes exactly, and the numbers in the inspector are the truth. ### Steps 1. Set up the SVG canvas and viewBox transform Pan and zoom through the viewBox so what you see is the geometry you will export. Files: `src/canvas.ts` ```sh npm create tauri-app@latest ``` 2. Keep full precision in the document Store floating point, display rounded. Rounding on write is what causes drift. Files: `src/document.ts` 3. Implement snapping with visible guides Snap to object edges, centres and a grid. Show the guide, because invisible snapping feels like the tool fighting you. Files: `src/snap.ts` ### Done when - [ ] A shape moved and returned reports its original coordinates - [ ] Four ninety degree rotations return the original geometry - [ ] Snap guides are visible during a drag - [ ] The inspector values match the document values ### Watch out - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. ## M2 · Boolean operations and path editing You can combine shapes into new ones and edit the resulting paths. ### Steps 1. Add the boolean library Flatten curves to the library's expected form, operate, and convert back. The conversion at each boundary is where precision is lost, so do it once. Files: `src/boolean.ts` 2. Keep operands for non-destructive editing A boolean result references its inputs. Un-combining is then trivial and users expect it. Files: `src/boolean.ts` 3. Test the degenerate cases Shared edges, tangent overlaps, fully contained shapes and zero-area results. These are the cases that appear in real work. Files: `src/boolean.test.ts` ### Done when - [ ] Union of overlapping rectangles produces the correct outline - [ ] Subtraction leaves a hole rather than a separate shape - [ ] Rectangles sharing one edge union cleanly - [ ] Un-combining restores the original shapes ### Watch out - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. ## M3 · Components and overrides Changing a component changes every instance, except where an instance deliberately differs. ### Steps 1. Implement components and sparse overrides An instance is a component reference plus an override map. Nothing else. Files: `src/components.ts` 2. Resolve at render time Instances resolve from the component on every render, which is what makes propagation automatic rather than a synchronisation step. Files: `src/resolve.ts` 3. Handle a missing component visibly Name what is missing. Silence here produces documents with invisible holes. Files: `src/resolve.ts` ### Done when - [ ] A component change propagates to non-overridden instances - [ ] An overridden property is not affected by the component change - [ ] Overrides survive unrelated component edits - [ ] A deleted component leaves a named broken state ### Watch out - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. ## M4 · Auto-layout and text styles Content that grows reflows its container instead of overflowing it. ### Steps 1. Implement stack layout Direction, gap, padding and alignment, computed top down with children measured first. Files: `src/layout.ts` 2. Measure text with the real font Wait for font loading and re-measure on load. This is the single most common source of layout that is subtly wrong. Files: `src/text.ts` 3. Add styles and variables as references Layers reference a style by identifier. A literal value is a deliberate choice, not the default. Files: `src/styles.ts` ### Done when - [ ] Growing text grows its stacked frame - [ ] A text style change reflows every frame using it - [ ] A colour variable change updates references and leaves literals alone - [ ] Nested stacks reflow correctly ### Watch out - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. ## M5 · Export at real scale factors Assets come out at exactly the sizes platforms require, from the vector source every time. ### Steps 1. Define export presets per layer Format, scale and a naming pattern. Multiple presets per layer, because platforms want several. Files: `src/export.ts` 2. Rasterise from geometry per scale Render the vector at each scale factor. Never upscale a bitmap. Files: `src/raster.ts` 3. Verify sharpness, not just size Compare a two times export against an upscaled one times. The difference should be obvious, and if it is not, you are upscaling. ### Done when - [ ] A two times export has double the dimensions and more detail - [ ] Batch export applies the naming pattern to every marked asset - [ ] SVG export opens correctly elsewhere - [ ] Exporting twice produces identical files ### Watch out - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ## M6 · Handoff, versioning and file safety (production only) Developers can read the design, and a document is never lost to a crash. ### Steps 1. Save atomically Temporary file plus rename. Test by killing the process during a save. Files: `src/save.ts` 2. Build the handoff view Measurements between selected layers, resolved colours and text styles. Read-only, so it can be shared without risk. Files: `src/handoff.ts` 3. Make the format diff well Stable key ordering and one layer per line group, so a small change produces a small diff. This is a real advantage over binary formats and is worth protecting. Files: `src/serialise.ts` ### Done when - [ ] A crash during save leaves a complete file, old or new - [ ] The handoff view shows measurements and colours without editing - [ ] Moving one layer produces a small readable diff - [ ] Serialisation key order is stable across saves ### Watch out - Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. ===== OPERATIONS.md ===== # Operations · Sketch ## Backup Documents are files, so ordinary file backup covers them, and version control gives history. This is the practical benefit of a diffable text format over a binary one. ## Restore Open the file. If a document ever needs anything beyond itself to open, something has leaked out of the file that should not have. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. What is worth tracking is export determinism: keep fixture documents and diff their exports in continuous integration, so a geometry or font change that shifts output is caught. ## Incident checklist The realistic incident is a crash during save, which is why the write is atomic. The second is a font update changing metrics and reflowing layouts, so pin font versions and treat a font change as a visual release. 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 - [ ] Four rotations return the original geometry without drift - [ ] The boolean degenerate case tests pass, including shared edges - [ ] A component change is confirmed to propagate to non-overridden instances only - [ ] A two times export is confirmed sharper than an upscaled one times - [ ] A killed process during save leaves a complete file ## Launch constraint Do not market omitted Sketch 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. Default location for documents. Version control friendly because the format is JSON. DOCUMENTS_DIR=~/Documents/Designs # Required. Where batch exports are written. EXPORT_DIR=~/Documents/Designs/exports # Required. How close a drag must come before snapping engages, in screen pixels rather than document units. SNAP_TOLERANCE_PX=6 # Required. How often to save. Saves are atomic, so a shorter interval is safe. AUTOSAVE_INTERVAL_MS=30000 # Required. The single colour space this application works in. Stated rather than managed. COLOR_SPACE=srgb
# Sketch · indie build A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. 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 | | --- | --- | --- | | Shell | Tauri with a TypeScript front end | a real desktop application with filesystem access and a native file dialog | | Rendering | SVG in the DOM for the canvas | vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary | | Geometry | A boolean path library operating on flattened paths | union, subtract and intersect on Bezier paths is genuinely hard and not worth writing | | Document | A JSON document format, one file per document, human diffable | design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats | | Export | resvg for rasterising, with scale factors applied to the source geometry | rasterising once per scale from the vector source, never upscaling a bitmap | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the front end and the tooling. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The Rust toolchain** · free - Why: Tauri compiles the desktop shell and hosts the rasteriser. - Get it: rustup from rustup.rs, plus the platform packages Tauri lists. - Verify: A Tauri hello world builds and runs - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Export can embed or outline fonts, which is redistribution and is not permitted by every licence. - Get it: Use openly licensed families and keep the licence files. Check the embedding clause before Phase 5 if you use a commercial face. - Verify: Each bundled font's licence permits embedding, or export outlines text - [ ] **Two real interface designs to rebuild** · free - Why: Boolean operations, components and auto-layout only reveal their gaps against real work. A test file of three rectangles will not. - Get it: Rebuild two screens you have designed before, or two from an open design system. - Verify: Both are rebuilt and the friction points are noted - [ ] **A decision about colour space** · free - Why: Colour management is explicitly out of scope, so the document must state which space it works in or exports will not match expectations. - Get it: Pick one, usually standard RGB, and state it in the README and in the interface. - Verify: The colour space is stated in the README ## Quick start ```sh npm create tauri-app@latest ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. - color-management edge cases - cloud collaboration and mobile apps - pixel-perfect professional tooling - large template and asset libraries If one of those is essential to you, that is the reason to keep paying for Sketch, and the README should say so rather than pretend.
# Build brief · Sketch 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 local vector interface design editor with components, boolean operations and export at real scale factors to replace Sketch. 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) - Shell: Tauri with a TypeScript front end · a real desktop application with filesystem access and a native file dialog - Rendering: SVG in the DOM for the canvas · vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary - Geometry: A boolean path library operating on flattened paths · union, subtract and intersect on Bezier paths is genuinely hard and not worth writing - Document: A JSON document format, one file per document, human diffable · design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats - Export: resvg for rasterising, with scale factors applied to the source geometry · rasterising once per scale from the vector source, never upscaling a bitmap ### Data model (create this before Phase 1) - documents(id, name, path, updated_at) - pages(id, doc_id, name, order) - layers(id, page_id, parent_id, kind, name, x, y, w, h, rotation, opacity, visible, locked, z) · kind is frame, rect, ellipse, path, text, image, component or instance - styles(id, doc_id, kind, name, value_json) · kind is fill, text or effect, referenced by layers rather than copied - components(id, doc_id, root_layer_id, name), instances(layer_id, component_id, overrides_json) - An instance stores only its overrides. Everything else resolves from the component, which is what makes changing the component change every instance. ### Phase 1 · Canvas, shapes and precise geometry Build: An SVG canvas with pan and zoom through the viewBox. Rectangles, ellipses and paths with an inspector showing exact position and size. Snapping to other objects and to a pixel grid, with the snap visible while dragging. Trap: Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. Done when: A shape moved and returned reports its original coordinates exactly. A shape rotated ninety degrees four times returns to its starting geometry without drift. Snapping aligns edges to other objects and the alignment guide is visible during the drag. Do not build yet: No boolean operations, no components, no export. ### Phase 2 · Boolean operations and path editing Build: Union, subtract, intersect and difference over paths using a boolean library. A path editor for moving points and handles. Keep the operands so a boolean result can be un-combined, rather than destroying the inputs. Trap: Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. Done when: Union of two overlapping rectangles produces one path with the correct outline. Subtracting a circle from a rectangle leaves a hole rather than a separate shape. Two rectangles sharing exactly one edge union into a single shape without artefacts. Un-combining restores both original shapes. Do not build yet: No components or auto-layout yet. ### Phase 3 · Components and overrides Build: Turn a selection into a component. Place instances that resolve their content from the component. Allow overrides of text, fill and visibility per instance, stored as a sparse set rather than a copy. Editing the component updates every instance that has not overridden the changed property. Trap: Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. Done when: Editing a component's fill changes every instance that has not overridden the fill, and leaves overridden ones alone. An instance's override survives an unrelated component change. Deleting a component leaves instances in a clearly broken state that names the missing component rather than silently rendering nothing. Do not build yet: No auto-layout or export yet. ### Phase 4 · Auto-layout and text styles Build: Stack layout on frames: direction, spacing, padding and alignment, with children reflowing when content changes size. Text styles and colour variables referenced by name so a change updates everywhere it is used. Trap: Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: Adding text to a stacked frame grows the frame rather than overflowing. Changing a text style's size reflows every frame using it. A colour variable change updates every layer referencing it and leaves literal colours alone. Nesting a stack inside a stack reflows both correctly. Do not build yet: No export yet. ### Phase 5 · Export at real scale factors Build: Export presets per layer or frame: format, scale factor and naming pattern. Rasterise from the vector source at each scale rather than scaling a rendered bitmap. Batch export everything marked exportable in one action. Trap: Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. Done when: A two times export has exactly double the pixel dimensions of the one times, and is visibly sharper rather than merely larger. A batch export produces every marked asset with the naming pattern applied. An SVG export opens correctly in another application with text either embedded or outlined. Do not build yet: Sharing and handoff come next. ### Phase 6 · Handoff, versioning and file safety Build: A read-only inspection view exposing measurements, colours and text styles. Save atomically with a crash-safe write. Because the document format is diffable JSON, make version control the versioning story rather than building one. Trap: Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. Done when: A crash simulated during a save leaves either the previous document or the new one, never a truncated file. The handoff view shows measurements and colour values without allowing edits. A document committed to version control produces a readable diff when a single layer moves. Do not build yet: Nothing after this. ### Out of scope (and why) - Colour management across profiles. Getting colour identical between a design tool, a browser and a printer is a deep problem and this build works in one colour space and says so. - Real-time collaborative editing and the cloud workspace, which is where the industry moved and is a substantially larger project. - Prototyping with interactions and transitions, which is a second product bolted onto the first. - Plugin ecosystems and the third party tooling that has grown around the established formats. ### README must contain - That geometry is stored at full precision and rounded only for display and export - That instances store overrides rather than copies, and what that means when a component changes - That text measurement waits for real font loading - That every scale factor is rasterised from the vector source, never upscaled - That the document format is diffable JSON and version control is the versioning story
# Agent instructions · Sketch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri with a TypeScript front end, SVG in the DOM for the canvas, A boolean path library operating on flattened paths, A JSON document format, one file per document, human diffable, resvg for rasterising, with scale factors applied to the source geometry. 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 - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels.
# Build plan · Sketch A desktop vector editor for interface work: precise shapes with boolean operations, auto-layout stacks that reflow, reusable components with overrides, text styles and colour variables, and export presets that produce assets at the exact scale factors platforms require. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Canvas, shapes and precise geometry You can draw and position shapes exactly, and the numbers in the inspector are the truth. ### Steps 1. Set up the SVG canvas and viewBox transform Pan and zoom through the viewBox so what you see is the geometry you will export. Files: `src/canvas.ts` ```sh npm create tauri-app@latest ``` 2. Keep full precision in the document Store floating point, display rounded. Rounding on write is what causes drift. Files: `src/document.ts` 3. Implement snapping with visible guides Snap to object edges, centres and a grid. Show the guide, because invisible snapping feels like the tool fighting you. Files: `src/snap.ts` ### Done when - [ ] A shape moved and returned reports its original coordinates - [ ] Four ninety degree rotations return the original geometry - [ ] Snap guides are visible during a drag - [ ] The inspector values match the document values ### Watch out - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. ## Phase 2 · Boolean operations and path editing You can combine shapes into new ones and edit the resulting paths. ### Steps 1. Add the boolean library Flatten curves to the library's expected form, operate, and convert back. The conversion at each boundary is where precision is lost, so do it once. Files: `src/boolean.ts` 2. Keep operands for non-destructive editing A boolean result references its inputs. Un-combining is then trivial and users expect it. Files: `src/boolean.ts` 3. Test the degenerate cases Shared edges, tangent overlaps, fully contained shapes and zero-area results. These are the cases that appear in real work. Files: `src/boolean.test.ts` ### Done when - [ ] Union of overlapping rectangles produces the correct outline - [ ] Subtraction leaves a hole rather than a separate shape - [ ] Rectangles sharing one edge union cleanly - [ ] Un-combining restores the original shapes ### Watch out - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. ## Phase 3 · Components and overrides Changing a component changes every instance, except where an instance deliberately differs. ### Steps 1. Implement components and sparse overrides An instance is a component reference plus an override map. Nothing else. Files: `src/components.ts` 2. Resolve at render time Instances resolve from the component on every render, which is what makes propagation automatic rather than a synchronisation step. Files: `src/resolve.ts` 3. Handle a missing component visibly Name what is missing. Silence here produces documents with invisible holes. Files: `src/resolve.ts` ### Done when - [ ] A component change propagates to non-overridden instances - [ ] An overridden property is not affected by the component change - [ ] Overrides survive unrelated component edits - [ ] A deleted component leaves a named broken state ### Watch out - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. ## Phase 4 · Auto-layout and text styles Content that grows reflows its container instead of overflowing it. ### Steps 1. Implement stack layout Direction, gap, padding and alignment, computed top down with children measured first. Files: `src/layout.ts` 2. Measure text with the real font Wait for font loading and re-measure on load. This is the single most common source of layout that is subtly wrong. Files: `src/text.ts` 3. Add styles and variables as references Layers reference a style by identifier. A literal value is a deliberate choice, not the default. Files: `src/styles.ts` ### Done when - [ ] Growing text grows its stacked frame - [ ] A text style change reflows every frame using it - [ ] A colour variable change updates references and leaves literals alone - [ ] Nested stacks reflow correctly ### Watch out - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 5 · Export at real scale factors Assets come out at exactly the sizes platforms require, from the vector source every time. ### Steps 1. Define export presets per layer Format, scale and a naming pattern. Multiple presets per layer, because platforms want several. Files: `src/export.ts` 2. Rasterise from geometry per scale Render the vector at each scale factor. Never upscale a bitmap. Files: `src/raster.ts` 3. Verify sharpness, not just size Compare a two times export against an upscaled one times. The difference should be obvious, and if it is not, you are upscaling. ### Done when - [ ] A two times export has double the dimensions and more detail - [ ] Batch export applies the naming pattern to every marked asset - [ ] SVG export opens correctly elsewhere - [ ] Exporting twice produces identical files ### Watch out - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ## Not in this build - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. ## After v1, if you want it - Component variants, which are the natural next step once overrides work - A command line export so continuous integration can produce assets - Importing an existing design system's tokens as styles and variables
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Default location for documents. Version control friendly because the format is JSON. DOCUMENTS_DIR=~/Documents/Designs # Required. Where batch exports are written. EXPORT_DIR=~/Documents/Designs/exports # Required. How close a drag must come before snapping engages, in screen pixels rather than document units. SNAP_TOLERANCE_PX=6 # Required. How often to save. Saves are atomic, so a shorter interval is safe. AUTOSAVE_INTERVAL_MS=30000 # Required. The single colour space this application works in. Stated rather than managed. COLOR_SPACE=srgb
# Sketch · product brief ## Problem The visible interface design loop is buildable, but a credible replacement needs more than the first screen. Sketch earns its keep through editor polish, assets, algorithms, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A vector editor with precise geometry that does not drift, boolean operations that survive shared edges, components whose overrides are explicit, layout that reflows with real font metrics, and exports rasterised from vectors at every scale. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - sample user-owned assets ## Explicit non-goals for v1 - Colour management across profiles and devices. - Real-time collaboration and a cloud workspace. - Prototyping with interactions and transitions. - A plugin ecosystem. - color-management edge cases - cloud collaboration and mobile apps - pixel-perfect professional tooling - large template and asset libraries ## Success criteria - Four rotations return the original geometry without drift - The boolean degenerate case tests pass, including shared edges - A component change is confirmed to propagate to non-overridden instances only - A two times export is confirmed sharper than an upscaled one times - A killed process during save leaves a complete file
# Build brief · Sketch 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 local vector interface design editor with components, boolean operations and export at real scale factors to replace Sketch. 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) - Shell: Tauri with a TypeScript front end · a real desktop application with filesystem access and a native file dialog - Rendering: SVG in the DOM for the canvas · vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary - Geometry: A boolean path library operating on flattened paths · union, subtract and intersect on Bezier paths is genuinely hard and not worth writing - Document: A JSON document format, one file per document, human diffable · design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats - Export: resvg for rasterising, with scale factors applied to the source geometry · rasterising once per scale from the vector source, never upscaling a bitmap ### Data model (create this before Phase 1) - documents(id, name, path, updated_at) - pages(id, doc_id, name, order) - layers(id, page_id, parent_id, kind, name, x, y, w, h, rotation, opacity, visible, locked, z) · kind is frame, rect, ellipse, path, text, image, component or instance - styles(id, doc_id, kind, name, value_json) · kind is fill, text or effect, referenced by layers rather than copied - components(id, doc_id, root_layer_id, name), instances(layer_id, component_id, overrides_json) - An instance stores only its overrides. Everything else resolves from the component, which is what makes changing the component change every instance. ### Phase 1 · Canvas, shapes and precise geometry Build: An SVG canvas with pan and zoom through the viewBox. Rectangles, ellipses and paths with an inspector showing exact position and size. Snapping to other objects and to a pixel grid, with the snap visible while dragging. Trap: Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. Done when: A shape moved and returned reports its original coordinates exactly. A shape rotated ninety degrees four times returns to its starting geometry without drift. Snapping aligns edges to other objects and the alignment guide is visible during the drag. Do not build yet: No boolean operations, no components, no export. ### Phase 2 · Boolean operations and path editing Build: Union, subtract, intersect and difference over paths using a boolean library. A path editor for moving points and handles. Keep the operands so a boolean result can be un-combined, rather than destroying the inputs. Trap: Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. Done when: Union of two overlapping rectangles produces one path with the correct outline. Subtracting a circle from a rectangle leaves a hole rather than a separate shape. Two rectangles sharing exactly one edge union into a single shape without artefacts. Un-combining restores both original shapes. Do not build yet: No components or auto-layout yet. ### Phase 3 · Components and overrides Build: Turn a selection into a component. Place instances that resolve their content from the component. Allow overrides of text, fill and visibility per instance, stored as a sparse set rather than a copy. Editing the component updates every instance that has not overridden the changed property. Trap: Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. Done when: Editing a component's fill changes every instance that has not overridden the fill, and leaves overridden ones alone. An instance's override survives an unrelated component change. Deleting a component leaves instances in a clearly broken state that names the missing component rather than silently rendering nothing. Do not build yet: No auto-layout or export yet. ### Phase 4 · Auto-layout and text styles Build: Stack layout on frames: direction, spacing, padding and alignment, with children reflowing when content changes size. Text styles and colour variables referenced by name so a change updates everywhere it is used. Trap: Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: Adding text to a stacked frame grows the frame rather than overflowing. Changing a text style's size reflows every frame using it. A colour variable change updates every layer referencing it and leaves literal colours alone. Nesting a stack inside a stack reflows both correctly. Do not build yet: No export yet. ### Phase 5 · Export at real scale factors Build: Export presets per layer or frame: format, scale factor and naming pattern. Rasterise from the vector source at each scale rather than scaling a rendered bitmap. Batch export everything marked exportable in one action. Trap: Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. Done when: A two times export has exactly double the pixel dimensions of the one times, and is visibly sharper rather than merely larger. A batch export produces every marked asset with the naming pattern applied. An SVG export opens correctly in another application with text either embedded or outlined. Do not build yet: Sharing and handoff come next. ### Phase 6 · Handoff, versioning and file safety Build: A read-only inspection view exposing measurements, colours and text styles. Save atomically with a crash-safe write. Because the document format is diffable JSON, make version control the versioning story rather than building one. Trap: Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters. Done when: A crash simulated during a save leaves either the previous document or the new one, never a truncated file. The handoff view shows measurements and colour values without allowing edits. A document committed to version control produces a readable diff when a single layer moves. Do not build yet: Nothing after this. ### Out of scope (and why) - Colour management across profiles. Getting colour identical between a design tool, a browser and a printer is a deep problem and this build works in one colour space and says so. - Real-time collaborative editing and the cloud workspace, which is where the industry moved and is a substantially larger project. - Prototyping with interactions and transitions, which is a second product bolted onto the first. - Plugin ecosystems and the third party tooling that has grown around the established formats. ### README must contain - That geometry is stored at full precision and rounded only for display and export - That instances store overrides rather than copies, and what that means when a component changes - That text measurement waits for real font loading - That every scale factor is rasterised from the vector source, never upscaled - That the document format is diffable JSON and version control is the versioning story
# Architecture · Sketch ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri with a TypeScript front end | a real desktop application with filesystem access and a native file dialog | | Rendering | SVG in the DOM for the canvas | vector editing wants selectable, hit-testable nodes, and interface designs rarely reach the object counts where canvas becomes necessary | | Geometry | A boolean path library operating on flattened paths | union, subtract and intersect on Bezier paths is genuinely hard and not worth writing | | Document | A JSON document format, one file per document, human diffable | design files that cannot be diffed cannot be reviewed, and that is a real cost of the incumbent formats | | Export | resvg for rasterising, with scale factors applied to the source geometry | rasterising once per scale from the vector source, never upscaling a bitmap | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | Layers, pages, styles and serialisation. | JSON with stable ordering is a deliberate choice for diffability. A binary format would be faster and would give that up. | | Geometry | Transforms, snapping and precision. | The full-precision rule must survive any change. Rounding on write is the failure mode. | | Boolean | Path combination. | Library choice is replaceable; the degenerate case tests must come with it, because every library fails differently. | | Components | Definition, instances and override resolution. | Sparse overrides resolved at render is the design. A copy-based approach is simpler and loses the propagation guarantee. | | Export | Presets and rasterisation per scale. | Any rasteriser. Rendering from geometry at each scale is the correctness property. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DOCUMENTS_DIR` · required · Default location for documents. Version control friendly because the format is JSON. - `EXPORT_DIR` · required · Where batch exports are written. - `SNAP_TOLERANCE_PX` · required · How close a drag must come before snapping engages, in screen pixels rather than document units. - `AUTOSAVE_INTERVAL_MS` · required · How often to save. Saves are atomic, so a shorter interval is safe. - `COLOR_SPACE` · required · The single colour space this application works in. Stated rather than managed. ## 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 · Sketch product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri with a TypeScript front end, SVG in the DOM for the canvas, A boolean path library operating on flattened paths, A JSON document format, one file per document, human diffable, resvg for rasterising, with scale factors applied to the source geometry. - 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 - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. - Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters.
# Delivery milestones · Sketch Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Canvas, shapes and precise geometry You can draw and position shapes exactly, and the numbers in the inspector are the truth. ### Steps 1. Set up the SVG canvas and viewBox transform Pan and zoom through the viewBox so what you see is the geometry you will export. Files: `src/canvas.ts` ```sh npm create tauri-app@latest ``` 2. Keep full precision in the document Store floating point, display rounded. Rounding on write is what causes drift. Files: `src/document.ts` 3. Implement snapping with visible guides Snap to object edges, centres and a grid. Show the guide, because invisible snapping feels like the tool fighting you. Files: `src/snap.ts` ### Done when - [ ] A shape moved and returned reports its original coordinates - [ ] Four ninety degree rotations return the original geometry - [ ] Snap guides are visible during a drag - [ ] The inspector values match the document values ### Watch out - Rounding positions to integers on every drag makes rotation and scaling accumulate error, so a shape drifts after a few operations. Keep full precision in the document and round only for display and for export, where the pixel grid actually matters. ## M2 · Boolean operations and path editing You can combine shapes into new ones and edit the resulting paths. ### Steps 1. Add the boolean library Flatten curves to the library's expected form, operate, and convert back. The conversion at each boundary is where precision is lost, so do it once. Files: `src/boolean.ts` 2. Keep operands for non-destructive editing A boolean result references its inputs. Un-combining is then trivial and users expect it. Files: `src/boolean.ts` 3. Test the degenerate cases Shared edges, tangent overlaps, fully contained shapes and zero-area results. These are the cases that appear in real work. Files: `src/boolean.test.ts` ### Done when - [ ] Union of overlapping rectangles produces the correct outline - [ ] Subtraction leaves a hole rather than a separate shape - [ ] Rectangles sharing one edge union cleanly - [ ] Un-combining restores the original shapes ### Watch out - Boolean operations on curves are numerically delicate, and shapes that share an exact edge or overlap at a tangent will produce artefacts or empty results in every library. Test with adjacent rectangles sharing an edge, because that case is common in interface work and is where naive usage breaks. ## M3 · Components and overrides Changing a component changes every instance, except where an instance deliberately differs. ### Steps 1. Implement components and sparse overrides An instance is a component reference plus an override map. Nothing else. Files: `src/components.ts` 2. Resolve at render time Instances resolve from the component on every render, which is what makes propagation automatic rather than a synchronisation step. Files: `src/resolve.ts` 3. Handle a missing component visibly Name what is missing. Silence here produces documents with invisible holes. Files: `src/resolve.ts` ### Done when - [ ] A component change propagates to non-overridden instances - [ ] An overridden property is not affected by the component change - [ ] Overrides survive unrelated component edits - [ ] A deleted component leaves a named broken state ### Watch out - Storing an instance as a full copy with a link back makes a component change ambiguous: you cannot tell which differences were deliberate and which are stale. Store only the overrides, and resolve everything else from the component every time it is rendered. ## M4 · Auto-layout and text styles Content that grows reflows its container instead of overflowing it. ### Steps 1. Implement stack layout Direction, gap, padding and alignment, computed top down with children measured first. Files: `src/layout.ts` 2. Measure text with the real font Wait for font loading and re-measure on load. This is the single most common source of layout that is subtly wrong. Files: `src/text.ts` 3. Add styles and variables as references Layers reference a style by identifier. A literal value is a deliberate choice, not the default. Files: `src/styles.ts` ### Done when - [ ] Growing text grows its stacked frame - [ ] A text style change reflows every frame using it - [ ] A colour variable change updates references and leaves literals alone - [ ] Nested stacks reflow correctly ### Watch out - Text measurement must use the actual font, loaded and ready, or a stack sized against a fallback font will be visibly wrong the moment the real face arrives. Wait for font loading before measuring, and re-measure when a font finishes loading. ## M5 · Export at real scale factors Assets come out at exactly the sizes platforms require, from the vector source every time. ### Steps 1. Define export presets per layer Format, scale and a naming pattern. Multiple presets per layer, because platforms want several. Files: `src/export.ts` 2. Rasterise from geometry per scale Render the vector at each scale factor. Never upscale a bitmap. Files: `src/raster.ts` 3. Verify sharpness, not just size Compare a two times export against an upscaled one times. The difference should be obvious, and if it is not, you are upscaling. ### Done when - [ ] A two times export has double the dimensions and more detail - [ ] Batch export applies the naming pattern to every marked asset - [ ] SVG export opens correctly elsewhere - [ ] Exporting twice produces identical files ### Watch out - Rendering once at one times and scaling up to produce the two and three times assets gives you blurry output, which is the exact problem vector sources exist to avoid. Rasterise from the geometry at each scale factor separately, and verify by checking that the larger asset has genuinely more detail rather than more pixels. ## M6 · Handoff, versioning and file safety (production only) Developers can read the design, and a document is never lost to a crash. ### Steps 1. Save atomically Temporary file plus rename. Test by killing the process during a save. Files: `src/save.ts` 2. Build the handoff view Measurements between selected layers, resolved colours and text styles. Read-only, so it can be shared without risk. Files: `src/handoff.ts` 3. Make the format diff well Stable key ordering and one layer per line group, so a small change produces a small diff. This is a real advantage over binary formats and is worth protecting. Files: `src/serialise.ts` ### Done when - [ ] A crash during save leaves a complete file, old or new - [ ] The handoff view shows measurements and colours without editing - [ ] Moving one layer produces a small readable diff - [ ] Serialisation key order is stable across saves ### Watch out - Writing directly over the document file means a crash mid-save leaves a truncated file and loses the whole document. Write to a temporary file and rename over the target, which is atomic on every platform that matters.
# Operations · Sketch ## Backup Documents are files, so ordinary file backup covers them, and version control gives history. This is the practical benefit of a diffable text format over a binary one. ## Restore Open the file. If a document ever needs anything beyond itself to open, something has leaked out of the file that should not have. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. What is worth tracking is export determinism: keep fixture documents and diff their exports in continuous integration, so a geometry or font change that shifts output is caught. ## Incident checklist The realistic incident is a crash during save, which is why the write is atomic. The second is a font update changing metrics and reflowing layouts, so pin font versions and treat a font change as a visual release. 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 - [ ] Four rotations return the original geometry without drift - [ ] The boolean degenerate case tests pass, including shared edges - [ ] A component change is confirmed to propagate to non-overridden instances only - [ ] A two times export is confirmed sharper than an upscaled one times - [ ] A killed process during save leaves a complete file ## Launch constraint Do not market omitted Sketch 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. Default location for documents. Version control friendly because the format is JSON. DOCUMENTS_DIR=~/Documents/Designs # Required. Where batch exports are written. EXPORT_DIR=~/Documents/Designs/exports # Required. How close a drag must come before snapping engages, in screen pixels rather than document units. SNAP_TOLERANCE_PX=6 # Required. How often to save. Saves are atomic, so a shorter interval is safe. AUTOSAVE_INTERVAL_MS=30000 # Required. The single colour space this application works in. Stated rather than managed. COLOR_SPACE=srgb
$ choose a build depth, inspect the files, then open the complete pack in your agent
Sketch: Creative users pay for speed, output fidelity, nondestructive editing, and years of edge-case work hidden behind direct manipulation.
xcolor-management edge cases
xcloud collaboration and mobile apps
xpixel-perfect professional tooling
xlarge template and asset libraries
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Sketch →· no votes, no pay-to-list · just what's real
Sketch pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| standard | — | $12/user | 1 workspace and 50 GB storage/editor. |
| professional | — | $24/user | 1 workspace, 50 GB/editor and SSO. |
| enterprise | — | $44/user | 1 workspace, unlimited storage, SCIM and BYOK; custom review for 15+ editors. |
| private cloud | custom | — | Unlimited workspaces/storage with private deployment; custom price plus one-time setup/requirements charges. |
| mac-only license | custom | — | Perpetual Mac app licence for 1 seat with 1 year of updates; no web collaboration, sharing or iOS preview. |
free tierno free tier; a 30-day trial is offered and viewers are free
billingmonthly + annual subscriptions; Mac-only licence is a one-time purchase
hidden costsAfter subscription cancellation, a workspace becomes read-only and is available for download for 90 days before deletion. Private Cloud adds setup/custom-requirement charges. Direct purchases in India are unavailable from July 15, 2026 and require a partner.
verified 2026-08-14 · source ↗
Vibecode Sketch
Kinda. The core of Sketch is buildable in a weekend with the prompt on this page, but there are real gaps: color-management edge cases, cloud collaboration and mobile apps. Read the honest list above before committing.
How much does Sketch cost?
Sketch costs about $12/month (Standard, checked 2026-07-31), which is $144 per year.
What do I lose by replacing Sketch?
Honestly: color-management edge cases; cloud collaboration and mobile apps; pixel-perfect professional tooling; large template and asset libraries. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Sketch?
Yes: Penpot (Browser-based interface design, components, libraries, prototypes, comments, and multiplayer, with a credible import-and-self-host story.) Quant-UX (A useful free prototype-and-test workspace with collaboration and analytics; less capable for production-grade visual systems.) Lunacy (A polished native desktop UI editor with components, prototypes, cloud files, and small-team collaboration, free indefinitely.) All 3 curated free alternatives are at vibecodeit.com/sketch/alternatives. The prompt is for when you want it exactly your way.