No description
  • TypeScript 89.5%
  • CSS 9.1%
  • JavaScript 1.3%
  • HTML 0.1%
Find a file
Andras Schmelczer 8bf3e3b705
Some checks failed
Deploy to Pages / build (push) Failing after 10m45s
Simplify
2026-09-11 23:27:29 +01:00
.forgejo/workflows landing 2026-09-11 20:34:36 +01:00
benchmarks Simplify 2026-09-11 23:27:29 +01:00
e2e Simplify 2026-09-11 23:27:29 +01:00
public landing 2026-09-11 20:34:36 +01:00
scripts better patterns 2026-09-11 22:46:23 +01:00
src Simplify 2026-09-11 23:27:29 +01:00
tests Simplify 2026-09-11 23:27:29 +01:00
.gitignore wip 2026-09-11 16:09:28 +01:00
.prettierignore wip 2026-09-11 16:09:28 +01:00
.prettierrc.json wip 2026-09-11 16:09:28 +01:00
index.html landing 2026-09-11 20:34:36 +01:00
package-lock.json better 2026-09-11 22:14:24 +01:00
package.json better 2026-09-11 22:14:24 +01:00
playwright.config.ts wip 2026-09-11 16:09:28 +01:00
README.md Simplify 2026-09-11 23:27:29 +01:00
tsconfig.json Simplify 2026-09-11 23:27:29 +01:00
vite.config.ts wip 2026-09-11 16:09:28 +01:00

Floor planner

A single-page flooring and skirting planner built with Vite, React and TypeScript. All measurements use millimetres; areas are displayed in square metres. Run it locally or host dist/ on any static web host.

npm install
npm run dev

Use a supported Node.js release (22.12+ or 24+). Open the local URL printed by Vite.

The home page introduces the planner, a short DIY laminate fitting guide, and the difference between scotia and skirting boards. Plan my floor opens the app at ?planner, resuming the last locally saved project when available. Current project queries open the planner directly. Both pages work on static hosting without server-side routing. The planner loads only when opened.

npm test                     # Pure geometry, planning, URL and rendering tests
npx playwright install chromium
npm run test:e2e              # Desktop Chromium and mobile-sized Chromium
npm run build                # Strict TypeScript check and production build
npm run check                # Build, unit tests and browser tests
npm run benchmark            # Compare cut-first optimisation with the fixed 84-case baseline
npm run benchmark -- --random # Compare appearance-first optimisation with the same baseline

Planning workflow

New plans start with a random laying pattern. Click the numbered room, materials and cut-plan headings to expand or collapse each panel. Click Custom to build an outline wall by wall; guidance below the shape choices explains how to begin. Advanced flooring and skirting settings expand separately when needed. Saved plans keep their chosen laying pattern.

  1. Start with a rectangle, fireplace recess, or custom outline. Enter wall lengths in order. The first wall runs east; the turn after each wall is positive for right and negative for left. For example, a clockwise rectangle has 90° turns. The final corner is derived from the closed outline. Insert a wall after any existing wall, drag its handle to reorder it, or use the up/down buttons on a phone or keyboard. Doors stay attached to their wall. Numeric fields keep a local draft while typing. Blur or Enter commits a number; Escape discards it. Empty or unfinished entries revert to the last committed value.
  2. Add door openings with a width and a distance from the named starting corner. Each doorway shows the neighbouring wall names and an arrow in the measurement direction. Include architrave/trim in the width where skirting must be omitted. Doors terminate skirting; laminate ends at the room outline with a transition and expansion gap. Rooms are planned independently.
  3. For a custom outline, the closing-wall suggestion gives the required length and turn. Add it, then replace the suggested length with the actual measured length. An outline must close within the chosen tolerance and have no intersections or invalid doors before a cut plan can be produced.
  4. Set material dimensions, spacer, kerf and minimum piece sizes alongside the measurements. Kerf means material removed by the blade: a 3 mm cut leaves 600 mm + 397 mm from a 1,000 mm board. Both kerf fields have keyboard and touch tooltips. Set one purchased skirting board length for the whole project. Materials are shared across rooms; direction is per room.
  5. Room measurements, materials and cut plans open and close independently on the same page. The drawing stays pinned on the right while you scroll the panels; on phones a compact drawing stays above them. The Floorboards / Skirting switch above the drawing shows one material at a time, with the whole drawing fitting without an internal scrollbar. The material choice is saved with the plan. Locating or searching for a cut switches to its material automatically; printing includes both materials. Warnings follow their room, even when rooms share a name. Every installed board is drawn, including full lengths without labels. Cut codes sit above the wall and skirting drawing; short pieces use nearby labels with pointers so their names remain visible. The compact drawing header shows only the room, area and material legend. Only cuts and reusable offcuts receive sequential codes starting at F1 for flooring and A1 for the first rooms skirting. Flooring and skirting cuts are grouped by the purchased board they come from, including offcuts reused across rooms. Group headings use the cut codes; internal stock IDs stay out of the interface. Pieces wrap side by side when space allows. Each piece has a small preview that opens dimensions directly on its shape. Skirting previews use equal scales on both axes, with close-ups of each end to show the actual mitre angle. Numbered skirting pieces and their labels open cut details; uncut lengths use a neutral fill. Print all room drawings followed by flooring and skirting cut sheets grouped by purchased board. Every cut is included once, with its room and row or wall.
  6. Find a cut by its code, or search by room name. Searching several cut codes together, such as F27 + F28, finds their pieces together. Show on plan highlights the source boards pieces or an individual cut, with room buttons when a source is reused across rooms. A Search active or Location active banner stays above the pinned drawing; Show all pieces clears it. Typed search text and explicit board locations are saved separately. Source boards have Mark done checkboxes; completion marks appear on their list entries, installed pieces and printed sheets. Room search results also let you locate and mark uncut boards without adding cut labels to them. Measurements, wall order, flooring direction or material changes clear completion because the allocation changes; renaming rooms keeps it.
  7. Copy the link to save or share the complete project. A version 3 JSON payload in ?plan=... stores measurements, materials, completion marks and view choices. Piece selections include their material, so flooring F1 and a later rooms skirting F1 open the correct preview. Existing version 2 query links still open through a compatibility importer and are saved in the new format. Reloading or opening the link reproduces the plan. Browser back/forward restores room changes. The same link is saved automatically in localStorage under floor-planner.project. Opening the app without a project query restores the last saved plan; an explicit project query takes precedence. If localStorage is unavailable or full, URL saving still works. There is no account, API or database. Fonts are system fonts; the app makes no external font requests.

Calculation rules and practical limits

  • Geometry follows measured vectors, checks final-wall length/direction, detects crossings and validates opening bounds and overlaps. A small accepted closure residual snaps only the final wall to the origin, with a visible warning. A mismatch can indicate measurement error or an irregular wall; lengths and corner angles alone cannot identify or reconstruct a bow. Remeasure and represent known changes of direction as additional segments.
  • Polygon offsets and clipping use clipper-lib at 0.001 mm internal precision. Displayed cut dimensions round up to the next 0.5 mm; angles display to 0.1°. New cutting blanks reserve that rounded length plus kerf so the displayed cuts fit their source board. Finished polygon coordinates retain their original precision. The supplied cut templates contain board-relative polygon vertices and edge lengths for angled and notched pieces.
  • Laminate is inset from every wall, including fireplace recesses. Interior rows retain full factory width. When the final row would be narrower than the minimum, the first and last rows share the width equally. A bounded search also compares other feasible edge-row arrangements, including row seams aligned with inward corners to avoid unnecessary notches and tapered tips.
  • Brick uses a half-board stagger. Random is deterministic and discourages staircase runs, near-equal successive steps and repeating joint alignments, including steps that wrap across a board length. H-pattern avoidance checks joints two rows apart at the scale of a board width, so small misalignments still count as a visible repeat. Pattern preferences never invalidate a physically valid plan when tight offset limits force repetition. The offset is the nearest end-joint distance on the adjacent row's grid, not a distance from a wall. Half a board is the largest possible nearest-joint offset. Settings that cannot be met produce an error instead of a partial layout.
  • Minimum laminate sizes apply to each clipped piece's bounding length/width. Irregular shapes can still have narrow tips or necks; their exact templates are available in the cut popups. Product-specific click systems and assembly sequences are not simulated.
  • Offcut reuse preserves the needed left/right factory ends. Only full-width end remnants are available for reuse; ripped side strips and shaped scraps are conservatively waste. Crosscuts include kerf. The stock count is actual allocated boards, not an area-only estimate. Waste includes kerf and unused material, including still-usable stock remainders.
  • Skirting internal corners use coped joints. The blank keeps the wall-corner long point instead of subtracting the receiving boards thickness; the cope preparation is half the measured turn. Earlier walls receive adjoining later walls, so installation order has no cycle. At square corners, a 4,000 mm wall keeps a 4,000 mm blank; with 18 mm skirting at both corners its exposed front length is 3,964 mm. Non-square receiving ends have a wall-fitting bevel. External corners use equal mitres. Minimum sizes check the exposed face and installed body after corner and door allowances. Preparation previews and dashed body edges guide marking; the actual moulding profile must be scribed and back-cut, since thickness alone does not define it. This follows Gary Katzs coping and long-point measurement guidance; the general-angle allowances are derived from intersecting wall faces.
  • Skirting cut lengths are the longest-point envelope. Cut square blanks first, then prepare the mitres and cope the indicated internal ends; this keeps reusable stock remainders square. Back lengths and diagram positions locate the wall-facing edge. The stock allocation includes crosscut kerf and counts additional bevel and coping operations.
  • Random flooring offers Minimise cuts and Maximise randomness, with the actual cut count and waste percentage beside each choice. Minimise cuts ranks saw passes first, then simpler cuts (fewer shaping passes, then fewer rips), visual repetition and finally purchased boards. Maximise randomness ranks visual repetition first, then the same cutting and waste priorities. The choice is saved with the project; changing it clears completion marks because stock allocation can change. Brick prioritises cutting effort and then waste.
  • Flooring uses a bounded beam search that preserves factory-ended offcuts for later exact fits. Completed candidates are repacked. Both options share clipping results, and the best completed candidates are compared before displaying totals. Skirting compares full-stock, balanced and offcut-aware splits, packed in decreasing length order, preferring fewer cuts and then less waste. Available offcuts can move the next walls joints. Minimum lengths are reserved on the exposed face and full-thickness body of angled and coped pieces. These are deterministic, auditable heuristics rather than guarantees of a global minimum. Room order remains an input. See the benchmark definitions and iteration results.
  • Board counts exclude an extra allowance for damage or mistakes. Spacer, minimum size and staggering defaults are editable starting values; follow the exact product's installation instructions. For example, Quick-Step's installation guidance provides product-specific manuals, and its room-transition guidance explains separate expansion joints.
  • Links are bounded to 20 rooms, 100 walls per room and 30 doors per wall. Flooring calculations are bounded to 2,000 purchased boards and 500 rows per room. Large plans may take longer; very large projects should be split into separate links. Long links may exceed the limits of a messaging service.

Code map

File Responsibility
src/lib/types.ts Inputs and derived result contracts
src/lib/defaults.ts Starter measurements and deterministic IDs
src/lib/geometry.ts Vector tracing, closure, validation and polygon offsets
src/lib/flooring.ts Row search, clipping, factory-end-aware stock allocation
src/lib/skirting.ts Corner allowances, run splitting and stock allocation
src/lib/url-state.ts Versioned payload encoding and import validation
src/lib/legacy-query.ts Read-only importer for existing version 2 links
src/lib/project-state.ts Edit actions and allocation invalidation
src/lib/planning.ts Reuse geometry and plans when their calculation inputs stay unchanged
src/lib/drawing-geometry.ts Shared bounds and collision primitives for drawing labels
src/lib/use-printing.ts Mount print sheets on native print events and release them afterward
src/lib/limits.ts Shared project, persistence and calculation bounds
src/lib/piece-labels.ts Collision-free cut-code placement and pointers for small pieces
src/lib/board-tracking.ts Exact board/cut matching and room-name search
src/lib/wall-editing.ts Wall insertion and reordering with stable IDs
src/lib/use-project.ts URL, localStorage and browser-history synchronization
src/components/ Editors, dimensioned SVGs, board groups and printable cut sheets
tests/ Pure calculation and state tests
e2e/ Actual browser workflows at desktop and mobile sizes

Only the inputs, completion marks and view selections are persisted. Incomplete numeric drafts exist only while editing a field. Geometry, plans, quantities, warning messages and drawings are derived. There are no timestamps or random-number calls in the planners. npm run build also checks unused variables and strict TypeScript types.

Room renames and completion marks reuse existing plans. Geometry is built once per changed outline and shared by both material planners. Cut groups and counts are derived once for the screen and print views. Printed sheets mount only when printing begins, including native keyboard printing.