Build Linearity Curve
KINDAback to the verdict
A vector editor whose hard problems are one path representation that every operation can trust, boolean operations that stay undoable, and an export that reports what it had to throw away.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the editor and its test suite.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - installfree
Why Boolean operations on Bezier paths are the part of this project most likely to produce quietly wrong geometry if written from scratch.
Get it Choose a library with a test suite covering tangential and self-intersecting cases, and read its issue tracker before committing.
Verify
A fixture of two tangentially touching circles returns a result rather than hanging - have readyfree
Why Import correctness is only demonstrable against files that use the commands you would rather not implement.
Get it Assemble files using elliptical arcs, smooth shorthands, nested transforms, even-odd fills and text, from your own work or from public domain artwork.
Verify
Each fixture imports and exports with a written report - decidefree
Why The target format cannot express it, so either you decide now or the exporter decides for you and nobody finds out until the artwork is printed.
Get it Read the current SVG Strokes draft, confirm stroke-alignment is still not normative in SVG 2, and write down your rule. open ↗
Verify
The rule is in the README and asserted in a test
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- documents(id, name, format_version, width, height, unit, created_at, updated_at) - nodes(id, document_id, parent_id, ordinal, kind, transform, visible, locked, name) · kind is group, path, text, image or boolean - paths(node_id, subpaths_json, fill_rule, closed) · cubic segments only, everything normalised into this shape at import - styles(node_id, fill_json, strokes_json, opacity, blend_mode) · a stroke carries width, alignment, caps, joins and dash pattern - boolean_ops(node_id, op, operand_order_json) · operands stay as hidden children so the operation is still undoable a year later - ops(id, document_id, ordinal, kind, payload_json, inverse_json) · undo replays inverses, and a drag is one coalesced entry - export_reports(id, document_id, target, at, dropped_json) · what the exporter could not represent, written every time you export - Stroke alignment is stored because the editor supports it and the export format does not. Export is where that gets resolved, loudly.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DOCUMENT_DIR | required | ./documents | Where saved documents live. This is the data you cannot regenerate. |
ARC_TOLERANCE | required | 0.01 | Maximum deviation in document units when converting arcs to cubics. Recorded in each document. |
HIT_TOLERANCE_UNITS | required | 3 | Pick radius in document units before scaling by zoom. |
HISTORY_MAX_BYTES | required | 64000000 | Ceiling for the undo log. Exceeding it trims the oldest entries and says so. |
EXPORT_REPORT_DIR | required | ./exports/reports | Where export reports are written. One per export, including the clean ones. |
The build, in order
One path representation and nothing else
Every curve in the document is a cubic Bezier, whatever the file it came from used.
One representation is the decision the rest of the editor rests on.
Files
src/geometry/normalise.tssrc/io/svg-import.tsAn unrecorded tolerance makes a round trip failure unarguable.
Files
src/geometry/arc-to-cubic.tstests/arc-tolerance.test.tsNested transforms are where coordinates quietly stop matching.
Files
src/geometry/transform.ts
done when · tick each as it passeswatch out- Keeping five segment types because the file format has them means boolean operations, offsetting, hit testing and simplification each need five cases, and the arc case is wrong in all four of them. Convert once at the door, keep cubics only, and treat the importer as the only code in the project that has ever heard of an elliptical arc.
Direct manipulation that stays smooth
Selecting, dragging and editing nodes runs against the document model at interactive speed.
Opacity and device pixel ratio both break the pixel approach.
Files
src/interaction/hit-test.tstests/hit-test.test.tsA hairline has to stay clickable when you are zoomed out.
Files
src/interaction/hit-test.tsA full repaint per pointer move is what makes an editor feel cheap.
Files
src/render/dirty.ts
done when · tick each as it passeswatch out- Hit testing by reading the pixel under the cursor picks the wrong node for anything with opacity, misses a shape whose stroke is wider than its fill, and returns nothing at all once the canvas is scaled for a high density display. Test the point against the geometry, with the tolerance scaled by zoom so a hairline stays clickable at every magnification.
Boolean operations that stay undoable
Union, subtract, intersect and exclude produce a node that still remembers its operands.
Writing your own is how you meet tangential intersections.
Files
src/geometry/boolean.tspackage.jsonThe operation stays editable for as long as the document lives.
Files
src/model/boolean-node.tsDestroying the operands should take a deliberate click.
Files
src/commands/flatten.tstests/boolean.test.ts
done when · tick each as it passeswatch out- Flattening a union into one path the moment the user clicks the button means fixing a mistake made ten operations ago requires redrawing both shapes from scratch, which is the single most common complaint about cheap vector editors. Keep the operands, recompute the result when either changes, and make flattening an action with a name.
Undo as a log of inverses
Every change is an operation with an inverse, and a drag is one entry rather than four hundred.
Not a snapshot. The difference is the whole memory budget.
Files
src/history/ops.tsOne drag is one thing the user did.
Files
src/history/coalesce.tsUndo bugs hide in the combination, not the single case.
Files
tests/history.property.test.ts
done when · tick each as it passeswatch out- Pushing a copy of the whole document onto a stack per change is fine for twenty shapes and unusable for two thousand, and the memory cost appears exactly when the document finally matters. Store the inverse of each operation instead, and treat a pointer drag as one operation recorded when the pointer is released.
Export that tells you what it droppedproduct builder
SVG, PDF and raster export work, and every feature the target cannot express is reported by name.
The format has no way to say inside or outside.
Files
src/io/svg-export.tstests/stroke-alignment.test.tsSilent loss is what makes export untrustworthy.
Files
src/io/export-report.tsA format version you cannot open is a document you lost.
Files
tests/fixtures/v1.doc.jsontests/migrate.test.ts
done when · tick each as it passeswatch out- A stroke aligned to the inside or the outside of a path has no representation in exported SVG: stroke-alignment was dropped from SVG 2 and lives in the SVG Strokes module, whose own editor's draft says the SVG 2 stroke definition is still the normative one, so exporters silently centre the stroke and the artwork comes back a half stroke width wrong. Outline the stroke into a path on export, and name the nodes you had to do it to in the report.
- Exporting text as text produces a file that renders in a different typeface on any machine without that font, and exporting it as outlines produces a file nobody can edit or search. Neither is a default: ask, record the choice in the export report, and embed or subset the font when the target format allows it.
That is the whole plan for Linearity Curve. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Real-time collaboration.
- A text shaping and layout engine of your own.
- Print colour, spot inks and separations.
- A template and asset library.
- cloud collaboration and mobile apps
- pixel-perfect professional tooling
- large template and asset libraries
- color-management edge cases
- Variable width strokes, which need the offsetting code to be correct before anything else is worth attempting
- A plugin surface over the operation log, which is the only extension point that keeps undo working
- Symbols and instances, which are a second tree over the same nodes and worth doing once booleans are settled
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.