Foundry Foundry

Routr — Project Design Doc

A retroactive design doc capturing the architecture, decisions, and lessons learned from building Routr. Last updated: March 2026


Overview

What Is This?

Routr is a browser-based CNC workshop planning app. Users design woodworking projects by defining boards, drawing shapes (cuts, pockets, drill holes, paths), applying edge treatments, and exporting G-code for their CNC machine. Think of it as the missing link between "I have a woodworking plan" and "my CNC is cutting it" — no CAD expertise required.

The app models a real woodworking shop: table saw for straight cuts, router for pockets and profiles, drill press for holes, planer for surfacing, band saw for freeform paths. Users think in terms of tools they already know, and Routr translates that into machine-ready G-code.

Who Is It For?

Hobby and prosumer CNC owners — specifically desktop/benchtop machine users (Shapeoko, X-Carve, Altmill, Onefinity). These users know woodworking but aren't CAM experts. They want to go from a plan to cut parts without learning Fusion 360 or VCarve.

Pain points:

  • CAM software has a steep learning curve
  • Most tools are designed for machinists, not woodworkers
  • Going from a PDF plan to G-code is a multi-tool, multi-hour process
  • No tool thinks about the project as assembled furniture — they think about individual toolpaths

Business Model

Freemium on export. The entire app is free to use — design, simulate, preview. No account required. G-code export requires a subscription. Users invest time designing their project, which creates natural conversion pressure at the export moment.

Pricing tiers:

  • Free: full app access, 3 free exports
  • Early Adopter: $12/mo or $99/yr
  • Standard: $20/mo or $149/yr
  • Lifetime: $299

Product-led growth — the app is the sales pitch. No marketing site needed beyond a landing page with a CTA to the app.

Patent: Provisional application #63/998,973 filed March 6, 2026 (Workshop Mode methodology).


Product Vision

The Two Modes

Routr has two product modes that represent different approaches to CNC woodworking:

Assembly Mode was the original vision — a full furniture design-to-manufacture pipeline. Design individual boards, connect them with joints (box, dovetail, dado, rabbet, miter), preview the assembled result in 3D, nest boards onto stock sheets, and simulate the CNC toolpaths. Five steps: Design → Assembly → 3D Preview → Nesting → Simulation.

The problem was combinatorial complexity. Every combination of board shapes × joint types × edge connections created an enormous surface area. After months of development, Assembly Mode could essentially make boxes. Useful, but far from the vision.

Workshop Mode was the strategic pivot for launch. Instead of modeling entire assemblies, focus on single boards and think in terms of woodworking tools — table saw, router, drill press, planer, band saw. This dramatically reduced complexity while delivering immediate value. Workshop Mode is the launch product; Assembly Mode is the long-term growth path.

This pivot is one of the most important decisions in the project's history. It traded ambition for shippability — and it worked.

The Documentation Story

This project was built without design docs for the first several months. Code was the only source of truth. When CSDLC (Claymore Software Development Lifecycle) was formalized as a methodology, the first major application was creating retroactive design docs for Routr — reading the actual source code, documenting what exists, and surfacing inconsistencies that had accumulated without architectural documentation.

The docs you're reading now are the result. They serve as proof that design docs don't have to come first to be valuable — but they're dramatically more valuable when they do.

Future Pipelines

Beyond the two existing modes, Routr's vision includes additional input pipelines and platform plays:

  • PDF-to-Gcode — AI-powered pipeline that reads woodworking plan PDFs, extracts dimensions and cut lists, and generates G-code. The "moonshot" feature.
  • CAD-to-Gcode — Import CAD models and translate them into CNC operations.
  • Plan Marketplace — A platform for users to share and sell CNC-ready plans. The business model evolution: Workshop Mode gets users making things → PDF/CAD-to-Gcode expands what they can make → Marketplace lets them monetize and share. Think Etsy for CNC plans.

Patent Strategy

The woodworking CNC software space is remarkably under-patented. Vectric: zero patents. Carbide 3D: zero. Inventables: one (hardware only). This creates a significant first-mover advantage.

PatentStatusApplication #Summary
Workshop ModeFiled#63/998,973 (March 6, 2026)Operation-first paradigm — users think in woodworking tools, not CAM operations. 23 claims. Micro entity ($65).
PDF-to-Gcode PipelineDraftedAI document understanding → cut list extraction → G-code generation. 30 claims drafted.
Integrated Browser PipelineIdentifiedEnd-to-end reactive propagation in browser. Rated ⭐⭐⭐⭐⭐ novelty.
Assembly Constraint Solver + Joint GeometryIdentifiedAutomated joint generation from edge connections with constraint solving. Rated ⭐⭐⭐⭐ novelty.

Not patentable: Nesting (prior art since 1960s), heightmap simulation (expired 1990s patents).


Tech Stack

LayerTechnologyRationale
FrontendReact 18 + TypeScriptComponent model fits the multi-tab UI. TypeScript catches type errors in the complex data model — essential when shapes have type-specific params.
BuildViteFast HMR, simple config. No webpack complexity needed for a single-page app.
State ManagementZustandMinimal boilerplate, no provider wrapping, easy to persist. Single store keeps all project state co-located.
3D PreviewThree.js via @react-three/fiberReact bindings for Three.js. Used for the 3D assembly preview and toolpath simulator.
AuthSupabase AuthFree tier handles 50K MAU. Email/password + Google OAuth (Google not yet configured).
DatabaseSupabase (Postgres)User profiles, export tracking, promo codes. RLS policies enforce per-user data isolation.
PaymentsStripeCheckout Sessions + Customer Portal. Webhook → Cloudflare Pages Function → Supabase for plan activation.
DeploymentCloudflare PagesAuto-deploys from main branch. Edge-cached globally. Pages Functions for serverless API (checkout, webhooks, portal).
GeometryClipper.js (clipper2-js)Boolean geometry operations for SVG pocket detection and island subtraction. Handles contour offset pocketing.

Key Libraries & Dependencies

LibraryPurposeNotes
@react-three/fiberReact renderer for Three.js3D preview + simulator visualization
@react-three/dreiThree.js helpers (OrbitControls, etc.)Camera controls, view cube
clipper2-jsPolygon boolean ops + offsetSVG pocket detection, island subtraction, contour pocketing
zustandState managementSingle global store (useProjectStore)
vitestTest framework634+ tests, run with npm test -- --run
jsdomDOM environment for testsRequired for component tests

System Architecture

Architecture Diagram

graph TD
    subgraph UI["UI Layer (React)"]
        BS[Board Setup]
        NC[Nesting Canvas]
        AC[Assembly Canvas]
        SIM[Simulator / 3D Preview]
        BS & NC & AC & SIM --> ZS
        ZS["Zustand Store (useProjectStore)<br/>project: boards, links, toolSettings, placements, ..."]
    end

    subgraph ENGINE["Engine Layer"]
        TP[Toolpath Generators]
        GC[G-code Generator]
        AS[Assembly Solver]
        SVG[SVG Parser / Import]
        NE[Nesting Engine]
        TL[Tool Library]
        HM[Simulator Heightmap]
        ET[Edge Treatments]
    end

    subgraph EXT["External Services"]
        SB[Supabase Auth + DB]
        ST[Stripe Payments]
        CF[Cloudflare Pages]
    end

    ZS --> ENGINE
    ENGINE --> EXT

Layer Descriptions

UI Layer — React components organized by tab. Each tab has its own canvas and right panel. Components are presentational; business logic lives in the engine layer or store actions.

  • Board Setup — Primary design surface. Users add boards, draw shapes (rectangles, circles, paths, holes, slots), set cut types, configure tools. This is where most time is spent.
  • Nesting — Arranges boards onto stock sheets for material efficiency. Auto-nest algorithm + manual placement.
  • Assembly — Visual edge-clicking interface for connecting boards with joints (box, dovetail, dado, rabbet, miter). Constraint solver detects over-constrained and unreachable boards.
  • Simulator / 3D Preview — Three.js-powered toolpath visualization with playback controls. Heightmap-based material removal simulation. 3D assembly preview shows how boards fit together.

Zustand Store — Single global store (useProjectStore) holds all project state. Additional stores for auth (useAuthStore), theming (useThemeStore), and tool library (useToolLibraryStore). The project store is the source of truth — components read from it, engine functions receive data from it.

Engine Layer — Pure TypeScript functions with no React dependencies. This is where the real work happens:

  • engine/toolpath/ — Generates toolpath coordinates for each shape/operation type
  • engine/gcode/ — Converts toolpaths to G-code strings
  • engine/assembly/ — Constraint solver, edge validation, auto-link inference
  • engine/svg/ — SVG file parsing and import (path commands, bezier flattening, nesting detection)
  • engine/nesting/ — Auto-nest algorithm, placement validation
  • engine/tools/ — Tool library management, V-bit geometry calculations
  • engine/simulator/ — Heightmap-based material removal for visual simulation

External Services — Auth, database, and payments are all external. The app is a static SPA with no backend server — Cloudflare Pages Functions handle the serverless API needs (Stripe checkout sessions, webhooks, customer portal).

Data Flow: Design to G-code

graph TD
    A[User draws shapes on board] --> B["Shapes stored in Board.shapes[] (Zustand store)"]
    B --> C["'Generate Toolpaths' triggers workshopOperations.ts"]
    C --> D["Each shape → appropriate toolpath generator<br/>(straightCut, pocketCuts, drillCuts, etc.)"]
    D --> E["Toolpath generators produce<br/>ToolpathOperation[] with coordinates"]
    E --> F["Operations ordered:<br/>profiles → joints → pockets → drills → edge treatments"]
    F --> G["Each operation → G-code generator (generator.ts)"]
    G --> H["G-code blocks concatenated with<br/>tool change headers between operations"]
    H --> I["Final G-code exported as .nc file"]

Data Model

Core Entities

The data model is centered on the Project, which contains everything needed to fully describe a woodworking project.

graph TD
    P[Project] --> B["boards: Board[]"]
    P --> L["links: BoardLink[]"]
    P --> TS["toolSettings: ToolSettings"]
    P --> SS["stockSheets: StockSheet[]"]
    P --> NS[nestingSettings]
    P --> TAB["tabSettings: TabSettings"]
    P --> PL["placements: BoardPlacement[]"]

    B --> DIM[dimensions]
    B --> SH["shapes: Shape[]"]
    B --> J["joints: Joint[]"]
    B --> ET["edgeTreatments: EdgeTreatment[]"]

Key Design Decisions in the Data Model

Shapes are polymorphic via params. All shapes share common fields (id, type, cutType, position, depth, rotation, scale) but have type-specific params (RectParams, CircleParams, PathParams, etc.). This means you must check shape.type before accessing type-specific params — a lesson learned from multiple bugs.

Joints live on both boards AND links. A BoardLink connects two boards at specific edges and holds shared jointParams. When a link is created, matching joints are generated on both boards. Modifying the link's params updates both boards' joints.

All internal dimensions are millimeters. Display conversion happens at the UI layer based on the units state (metric | imperial). Engine functions always receive and return mm.

SVG imports use svgGroupId. Multi-path SVG files import as multiple shapes that share a svgGroupId. They move together as a unit. The isNested and isIsland flags handle SVG pocket detection (filled shapes = pockets, white fills = islands to leave uncut).

State Management

Zustand with no middleware. The store is a single flat object with action functions. No Redux-style reducers, no middleware, no devtools. This was a deliberate choice for simplicity — the data model is complex enough without adding state management complexity on top.

No persistence layer. Projects exist only in browser memory. No save/load, no cloud sync (yet). Export is the persistence mechanism — users export G-code and can re-create projects. This is a known limitation and future epic.

Derived state is computed on the fly. Toolpath operations, nesting validation, assembly solver results — all computed when needed rather than cached in the store. This avoids stale-state bugs at the cost of recomputation.


Deployment & Infrastructure

Environments

EnvironmentURLPurposeDeploy Trigger
Local127.0.0.1:5173Developmentnpm run dev
Stagingstaging.routr.shopPre-production, gated by Cloudflare AccessAuto-deploy on push to main
Productionapp.routr.shopLive (not yet launched)Manual promotion from staging

CI/CD Pipeline

Cloudflare Pages auto-builds and deploys on every push to main. No separate CI — build failures block deployment. Tests run locally before push (enforced by process, not automation).

Infrastructure Dependencies

ServicePurposeWhat Breaks If Down
Cloudflare PagesHosting + CDN + serverless functionsEntire app unavailable
SupabaseAuth + user profiles + export trackingCan't sign in, can't track exports, free tier stops working
StripePayment processingCan't upgrade, can't manage subscriptions

Serverless Functions (Cloudflare Pages Functions)

FunctionPurpose
create-checkout-sessionCreates Stripe Checkout session for plan upgrade
webhookReceives Stripe events, updates Supabase user plan
portalCreates Stripe Customer Portal session for subscription management

Security Model

Authentication & Authorization

  • Auth provider: Supabase Auth (email/password, Google OAuth planned)
  • Auth is optional for the app. Users can design freely without an account. Auth is required only for G-code export.
  • Export gating: First 3 exports free (tracked in Supabase user_profiles), then paywall triggers.
  • Staging access: Cloudflare Access restricts staging.routr.shop to an email allowlist.

Data Protection

  • No sensitive user data stored in-browser. Projects are ephemeral (browser memory only).
  • Supabase RLS policies enforce per-user data isolation on user_profiles.
  • Stripe handles all payment data. No card numbers touch our infrastructure.
  • API keys: Stripe secret key and Supabase service key are Cloudflare environment variables, never exposed to the client.

Known Attack Surfaces

  • Feedback endpoint: Rate-limited to 3 bug reports + 3 feature requests per month per user (auth-gated).
  • Export counter: Server-side enforcement via Supabase. Client-side checks are cosmetic only.
  • No server-side input validation on G-code: The app generates G-code client-side. A malicious user could craft dangerous G-code by modifying client state — mitigated by the export disclaimer (user assumes responsibility for verifying G-code before running on their machine).

Cross-Cutting Concerns

ConcernSummaryAffected EpicsDedicated Doc?
Coordinate SystemsScreen Y-down vs CNC Y-up vs Three.js scene coordinates. The flipY() transform in G-code export. Edge treatment top/bottom swap.G-code pipeline, Simulator, Edge treatments, SVG importYes — Coordinate Systems
Internal Units (mm)All dimensions stored in mm. Display conversion at UI layer. MM_PER_INCH = 25.4.All epicsN/A — simple convention
Tool Settings ResolutionOperations can use a tool from the library (toolId) or manual settings. Resolved at generation time. V-bit geometry has its own math (vBitGeometry.ts).Workshop tools, Edge treatments, SVG engraveN/A
Feature FlagsASSEMBLY_MODE_ENABLED, roundover disabled via feature flag. Controls what users see in production.Assembly, Edge treatmentsN/A
Release Flags (Staging vs Prod)Not yet implemented. Need environment-aware flags so features can be visible on staging but hidden in production. Currently feature flags are global — same behavior on staging and prod.All new featuresNeeded before post-launch feature development
Real-World ValidationG-code can only be fully validated by cutting on a physical CNC machine. Digital testing covers math and format but not real-world behavior (tool deflection, material variance, machine-specific quirks).G-code Pipeline, Workshop Mode, Edge TreatmentsYes — see Validation Strategy below

Risks & Constraints

Technical Risks

RiskLikelihoodImpactMitigation
Coordinate bugs in new featuresHighHigh — produces wrong G-code, damages material or machineDedicated coordinate systems doc, integration tests for all edges
Real-world validation bottleneckHighHigh — digital tests pass but G-code fails on machineLayered validation strategy (see below), G-code snapshot tests, simulator calibration
No staging release flagsMediumMedium — can't safely test new features in staging without exposing to prodImplement environment-aware feature flags before post-launch feature work
No project persistenceCertainMedium — users lose work on browser closePlanned epic for save/load
Client-side G-code generationCertainLow — users could craft bad G-codeExport disclaimer, terms of use, user assumes responsibility
Supabase free tier limitsLowMedium — 50K MAU, 500MB DBUnlikely to hit soon; upgrade path available

Known Limitations

  • No save/load. Projects exist only in browser memory.
  • No undo/redo (keyboard shortcuts exist for some actions but no full undo stack).
  • Origin point is fixed. Users cannot choose which corner of the board is the CNC origin — currently cuts assume a specific corner, which may be 180° opposite of the user's setup. This is a high-priority post-launch fix.
  • Roundover edge treatment disabled. Feature-flagged off due to depth calculation complexity. Chamfer, rabbet, and dado work.
  • Assembly mode is feature-flagged off. Works but not polished enough for launch.
  • No curved pocket toolpaths. Router pocket for freeform (path) shapes doesn't follow curves correctly.

Validation Strategy

Routr has a unique challenge: G-code changes can only be fully validated by cutting physical material on a CNC machine. Digital testing catches math and format errors, but real-world factors (tool deflection, material variance, machine quirks) can only be verified with actual cuts.

Layered validation approach (least → most expensive):

  1. Unit tests (automated) — Test the math. Toolpath coordinates, flipY transforms, V-bit depth calculations, bit offset logic. 634+ tests cover this layer. Catches most regressions.

  2. G-code snapshot tests (automated, TODO) — Generate G-code for a set of reference designs and compare against known-good snapshots. If output changes, test fails. Catches G-code pipeline regressions without cutting anything. This is the highest-ROI addition to the test suite.

  3. Simulator validation (visual) — The simulator is a virtual test cut. Once calibrated against real cuts, it becomes the regression test harness. Changes that look wrong in the simulator get caught before reaching the machine.

  4. Real-world validation cuts (physical, rare) — Required for: new operation types (first-ever cut of that kind), coordinate system changes, initial simulator calibration, and pre-launch sign-off. NOT required for every feature — snapshot tests and simulator handle regressions.

Reference project suite (TODO): A set of Routr projects that exercise every feature — one with edge treatments, one with SVG engrave, one with nesting, etc. Automated tests generate G-code for each, compare against snapshots, and flag drift.

Pre-launch minimum: One real validation cut per operation type (straight cut, pocket, drill, profile, edge treatment, SVG engrave). ~6-8 cuts total. This validates the toolpath generators and calibrates the simulator.

Tech Debt

  • Dead components: AssemblyPanel.tsx (old dropdown UI, replaced by AssemblyCanvas.tsx) still exists in code.
  • Operation reorder doesn't persist. Dragging operations in the simulator panel resets on "Generate Toolpaths."
  • Edge treatment depth calculations were patched multiple times. The current implementation works but the code path is convoluted (3-layer patch cycle during March 5-6 sprint).
  • No CI/CD tests. Tests run locally only. Build failures are the only automated gate.
  • Dead GitHub Pages deployment. PR preview deployments via GitHub Pages still exist from before the Cloudflare migration. Should be stripped out.
  • No staging release flags. Feature flags are global — no mechanism to show features on staging while hiding from prod. Needed before post-launch feature development.
  • No G-code snapshot tests. Highest-ROI missing test type. Would catch pipeline regressions without physical cuts.

Sub-system Index

Sub-systems are architectural boundaries or product-level capabilities. If you removed one, multiple unrelated features would break.

Sub-systemDocStatusSummary
Workshop Modeworkshop-mode.mdShipped (launch product)Operation-first paradigm — users think in woodworking tools
Assembly Modeassembly-mode.mdShipped (feature-flagged off)Multi-board furniture pipeline: Design → Assembly → 3D → Nesting → Sim
G-code Pipelinegcode-pipeline.mdShippedShape → toolpath → operation ordering → G-code export
Coordinate Systemscoordinate-systems.mdDocumentedScreen (Y-down), CNC (Y-up), Three.js — transforms and mapping
Simulatorsimulator.mdShippedToolpath visualization, heightmap material removal, playback

Epic Index

Epics are deliverable feature work built on top of sub-systems. Remove one and a specific capability disappears.

EpicDocStatusParent Sub-systemSummary
Edge Treatmentsedge-treatments.mdShipped (roundover disabled)G-code Pipeline, Coordinate SystemsChamfer, roundover, rabbet, dado on board edges
SVG Import & Engravesvg-import.mdShippedG-code PipelineSVG parsing, pocket detection, island subtraction, engrave
Nestingnesting.mdShippedAssembly ModeMulti-sheet stock layout, auto-nest algorithm
Assembly & Jointsassembly-joints.mdShipped (flagged off)Assembly ModeBoard linking, constraint solver, joint geometry
Auth & PaymentsShippedSupabase auth, Stripe checkout, export gating
ThemingShippedDark/light theme system

Decisions Log

DateDecisionRationaleAlternatives Considered
Early 2026Zustand over ReduxMinimal boilerplate, single flat store fits the data model. No middleware needed.Redux (too much ceremony), Jotai (atomic model doesn't fit project-as-single-entity), Context API (performance concerns with frequent updates)
Early 2026Client-side G-code generationNo server needed, works offline, simpler architecture. Users own their G-code.Server-side generation (adds latency, requires backend, no offline)
Early 2026Freemium on exportUsers invest time designing → high conversion at paywall. Full app access free builds trust and word-of-mouth.Free trial (time pressure kills exploration), pay upfront (kills adoption), ad-supported (wrong audience)
Feb 2026Cloudflare Pages over Vercel/NetlifyEdge caching, Pages Functions for serverless, DNS already on Cloudflare, generous free tier.Vercel (good but vendor lock-in concerns), Netlify (fewer edge features), GitHub Pages (no serverless functions)
Feb 2026Supabase over FirebasePostgres + RLS is more flexible than Firestore. Free tier is generous. SQL is a known quantity.Firebase (NoSQL doesn't fit relational data model), Auth0 (overkill for auth-only needs), custom backend (unnecessary complexity)
Mar 2026No project persistence for launchShip faster. Users are making one-off projects initially. Save/load adds significant complexity (versioning, migration, storage).LocalStorage (fragile, size limits), Supabase storage (adds backend complexity before validating product-market fit)
Mar 2026All internal units in mmSingle unit system eliminates conversion bugs in the engine. Display conversion is a UI concern only.Store in user's preferred unit (conversion bugs everywhere), dual storage (complexity)
Mar 2026Feature-flag assembly modeWorks but UX isn't polished. Don't ship half-baked.Ship as-is (bad first impression), cut entirely (waste of work)

Glossary

TermDefinition
Workshop ModeThe primary design paradigm — users think in terms of woodworking tools (table saw, router, drill press) rather than CAM operations (profile, pocket, drill).
BoardA physical piece of wood being designed. Has dimensions, shapes, joints, and edge treatments.
ShapeA cut, pocket, hole, path, or surfacing operation drawn on a board. Polymorphic via type + params.
Edge TreatmentA decorative or functional modification to a board edge (chamfer, roundover, rabbet, dado).
Stock SheetRaw material that boards are nested onto for cutting.
NestingArranging boards on stock sheets to minimize material waste.
PlacementA board's position and rotation on a specific stock sheet.
OperationA toolpath operation in the simulator (profile-cut, pocket, drill, etc.). Each has its own G-code block.
flipYThe coordinate transform applied during G-code export to convert screen coordinates (Y-down) to CNC coordinates (Y-up).
TabA small bridge of material left uncut during profile operations to prevent the part from shifting. Defined by width, height, and spacing.
BitOffsetWhether the tool path runs to the left, center, or right of the drawn line ('left' | 'center' | 'right').
V-bitA conical cutting tool used for chamfers and engraving. Requires special depth/width geometry calculations (vBitGeometry.ts).
IslandIn SVG import, a white-filled closed path inside a pocket region. Left uncut (material remains).
Lightning StrikeA CSDLC session where everything fires on all cylinders — tight refinement leads to rapid, clean execution. Multiple features ship in one session.

This design doc is the source of truth for Routr's project-level architecture. Epic-level details live in epics/. Implementation specifics live in the project repo's WORKFLOW.md. Update this doc when architecture changes — stale docs are worse than no docs.

Review

🔒

Enter your access token to view annotations