This commit is contained in:
Andras Schmelczer 2026-09-11 16:58:58 +01:00
commit 62a385e4c9
33 changed files with 2649 additions and 809 deletions

View file

@ -10,7 +10,7 @@ npm run dev
Use a supported Node.js release (22.12+ or 24+). Open the local URL printed by Vite.
```sh
npm test # Pure geometry, planning, URL and CSV tests
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
@ -19,18 +19,19 @@ npm run check # Build, unit tests and browser tests
## Planning workflow
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.
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 allow empty values and a minus sign while typing; unfinished entries revert to the last valid value when you leave the field.
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. Skirting stock lengths are purchase options, not a fixed inventory. Materials are shared across rooms; direction is per room.
5. The drawing and cut plan update on the same page. Flooring and skirting are visible together, with the whole drawing fitting the page without an internal scrollbar. Warnings follow their room, even when rooms share a name. Every installed board is drawn, including full lengths without labels. Only cuts and reusable offcuts receive sequential codes starting at F1 for flooring and A1 for the first room’s skirting. Flooring and skirting cuts are grouped by the purchased board they come from, including offcuts reused across rooms. 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. Export CSV in the same board order, or 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. Copy the link to save or share the complete project. Measurements, materials, selected room and selected piece live in readable query parameters such as `flooring.length=1200`, `room.1.wall.1.length=4800` and `selectedCut=F1`. You can edit values directly in the URL. Older compressed fragment links still load and migrate automatically; obsolete step, layer and zoom choices are ignored. Reloading or opening that link reproduces the plan. Browser back/forward restores room changes. There is no local storage, account, API, database or analytics. Fonts are system fonts; the app makes no external font requests.
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. Flooring and skirting are visible together, with the whole drawing fitting without an internal scrollbar. Warnings follow their room, even when rooms share a name. Every installed board is drawn, including full lengths without labels. Only cuts and reusable offcuts receive sequential codes starting at F1 for flooring and A1 for the first room’s 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 board or cut by its exact code, or search by room name. **Show on plan** highlights the source board’s pieces or an individual cut, with room buttons when a source is reused across rooms. Source boards have **Mark done** checkboxes; completion marks appear on their list entries, installed pieces and printed sheets. A full uncut board can also be found and marked by its source code. 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. Measurements, materials and view choices live in readable query parameters such as `flooring.length=1200`, `room.1.wall.1.length=4800` and `selectedCut=F1`. Progress uses repeated `done=floor:B4` or `done=skirting:S6` parameters; `search=F1` and repeated `collapsed=materials` parameters preserve the search and closed panels. You can edit values directly in the URL. Older compressed fragment links still load and migrate automatically; obsolete step, layer and zoom choices are ignored. Reloading or opening that link reproduces the plan. Browser back/forward restores room changes. There is no local storage, account, API, database or analytics. 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. List dimensions are rounded to 0.1 mm; cutting diagrams retain up to 0.001 mm. 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.
- 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. 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.
@ -42,18 +43,19 @@ npm run check # Build, unit tests and browser tests
## 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` | Readable query schema, legacy links and input validation |
| `src/lib/use-project.ts` | URL and browser-history synchronization |
| `src/lib/export.ts` | CSV generation, including formula-injection protection |
| `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 |
| 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` | Readable query schema, legacy links and input validation |
| `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 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 and view selections are persisted. 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.
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.