Release planning

CadCraft 1.0 impact map

Every fix and feature lined up for 1.0, and what each one touches: which apps, and which layers of the stack.

ProjectCadCraft
Sheet0.10 impact
Items110
Date2026-11-09
51Fix
22Feature
0Spike
28Platform
0Site ops
8Sanity check
1Decision
≈ 319days in the written 0.10 plans
  • Planned 101 on the plan for the release it is filed under
  • Built 0 built and working in /lab (the dev builds). Whether the public has it is its reach: "in the lab", "Live" (the last gate) or "switched off"; see docs/REACH-LIVE.md
  • Partly built 1 part of it is built in /lab; the rest waits (see "Waits on")
  • Promised 0 the live site already says it is coming
  • Moved 0 moved here from an earlier release or wishlist
  • Found 0 bug found after a release shipped; fixed in a point release or the next one
  • Candidate 3 recommended in a spike or session log; owner to choose
  • Community please test 0 built and in /use; a Discord post gives the steps and the expected result and asks people to try it on their own devices, files or know-how
  • Decision Time 5 a decision, check, file, key, flag or go only you can give; folds into the list once answered

Built means built and working in the lab. Live means the public can use it, and is the last gate: 0 Built · Live, 0 Built · in the lab.

Sheet 1 · impact areas

Where 0.10 lands

How many items touch each app and each layer, by kind. One item can touch several areas, so the rows add up to more than the item count. Pick a row to filter the schedule below.

Impact areaFixFeatureSpikePlatformSite opsSanity checkDecisionAll
Apps
CadCraft 20 17 – 10 – 3 1 51
BimCraft 18 20 – 9 – 4 1 52
ModelCraft 19 20 – 10 – 3 1 53
MarkupCraft 12 16 – 9 – 3 1 41
TakeoffCraft 14 16 – 9 – 3 1 43
CivilCraft 21 18 – 10 – 4 1 54
DetailCraft 15 17 – 9 – 4 1 46
PartCraft 15 17 – 9 – 3 1 45
Studio – 1 – – – – – 1
SuperCad Studio – 6 – 6 – 1 1 14
Layers
Site and services 13 3 – 8 – 1 – 25
Desktop host – 1 – 1 – – – 2
App shell 5 15 – 8 – 2 1 31
App code 23 4 – 3 – – – 30
Our engines 8 – – 1 – 1 – 10
Vendor engines – – – – – – – 0
Drawing surface 3 – – – – – – 3
Help and words 2 1 – – – 1 – 4
Tests and tooling 2 1 – 2 – 3 – 8

Sheet 2 · schedule

Every fix and feature

Grouped by track. App chips carry each app’s layer colour; Studio is in ink. "Waits on" names what has to happen first; the small lines under it say who asked and where the item is written up.

Add an item Fork map (Rust and C++ forks) The roadmap

Issues sync every 10 minutes; last sync Nov 9, 2026 7:00 pm.

ItemWhatAppsLayersStatusDaysWaits onIssues
Performance roadmap — wave 3 60
FixPERF.20 Link to PERF.20Comment on PERF.20 Scale: every text format reader holds the whole file as one JS string (DXF, LandXML, OBJ, DAE, ASCII STL, USDA, the native JSON documents)

Measured: a 1 GB DXF and a 692 MB LandXML die in TextDecoder.decode ("Cannot create a string longer than 0x1fffffe8"); a 358 MB LandXML needs 4.9 GB and 27 s; a 10 MB DXF needs 364 MB (38 bytes of heap per file byte, parsed twice through dxf-model.js:65). Sites: dxf-decode.js:35-49 and dxf.js:68-74, civilcraft.js:2485 and xml.js:27, modelcraft io/obj.js:84, and JSON.parse(await file.text()) for every .modelcraft/.detailcraft/.civilcraft document (modelcraft.js:3210). The caps that exist (CivilCraft 200 MB, ModelCraft 200 MB, OBJ 64 MB) are honest and stay until the readers stream. Change: byte-level tokenizers over Uint8Array fed from Blob.slice with lazily decoded values and Float64Array/Uint32Array output, the way lib/ifc scanStep already works. DXF first (the reader is 1,563 lines, L), then LandXML (M), OBJ and ASCII STL (S each). Target: 100 MB DXF under 1 GB of memory, 1 GB files open with a progress bar instead of an error.

CadCraftModelCraftCivilCraft App codeOur engines Planned 12 – docs/0.9-sessions/perf-wave3/
FixPERF.21 Link to PERF.21Comment on PERF.21 Scale: ModelCraft, DetailCraft, PartCraft and CivilCraft snapshot the whole document as JSON on every undo step and autosave

Measured: a 400 MB glTF (5 M faces) imports into ModelCraft in 17 s and 2.5 GB, then the FIRST edit calls model.js:2408 snap() (also :2223, :2230, :2272, :2331): 407 MB of JSON, 35.6 s, 9 GB peak, so the tab dies after a successful import. At 500k faces the snapshot is 3.1 s per edit. Same shape in DetailCraft model.js:169-199 (stringify before AND after every change), PartCraft part.js:504-517 and CivilCraft project.js:155. Change: command or diff undo (or per-definition dirty snapshots), meshes kept as typed buffers instead of base64 inside JSON, autosave payloads as Blobs of those buffers (the shell's IndexedDB store already takes Blobs). ModelCraft first. Extends PERF.7 with the 10x and 100x numbers.

ModelCraftCivilCraftDetailCraftPartCraft App code Planned 10 – docs/0.9-sessions/perf-wave3/
FixPERF.24 Link to PERF.24Comment on PERF.24 Scale: autosave rewrites the whole document every couple of seconds of editing, in every app (extends PERF.5)

Measured and read: CadCraft autosave writes a full DWG (dwg-autosave.js:39 saveDwg; 4.8 s today, 66 s at 10x), BimCraft the full IFC bytes (bimcraft.js:585 to model.js save), MarkupCraft the whole PDF Blob (markupcraft.js:4715; TakeoffCraft stops at 64 MB, takeoffcraft.js:4313), ModelCraft the JSON (modelcraft.js:3883). On a big file autosave is the longest task the person sees while working. Change: journal autosave, keeping the opened file's bytes or handle plus the edits map (IfcFile already holds exactly that in memory), snapshots taken in a worker from transferred buffers, and a clear cap with a message above a size. Shared piece in the shell's app-autosave.js, one snapshot function per app.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App shellApp code Planned 4 – docs/0.9-sessions/perf-wave3/
PlatformPERF.26 Link to PERF.26Comment on PERF.26 Scale: text writers return one string, so exports stop at 512 MB and hold twice the output

DXF (dxf.js:1324, dxf2000.js:437), LandXML (landxml.js:227), OBJ, DAE, USDA (usda-write.js:33), GeoJSON and gcode writers each build one string and return it; a 1,000,000-entity DXF export is near the V8 string limit and the string plus its Blob is twice the output in memory. Change: writers that yield chunks into a WritableStream (createWritable on a file handle when the host offers one, else a Blob built from parts), with the same report object. Pairs with the streaming readers row.

CadCraftModelCraftCivilCraft App codeOur engines Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.27 Link to PERF.27Comment on PERF.27 Scale: MarkupCraft reads the whole PDF twice and keeps the whole PDF per autosave; OBJ import refuses at 64 MB where the same model as GLB imports fine

Two small ones from the scale track. (1) pdf.js is healthy (2,000 sheets open in 0.6 s, lazy), but markupcraft.js:1158 and :1206 read the file twice, the second time only to count pages; and the autosave stores the whole PDF Blob each time: count pages from the first open, keep a file handle or the original bytes once. (2) ModelCraft io/obj.js:32 refuses OBJ above 64 MB (about 1.7 M faces) with an honest message while the same model as a 400 MB GLB imports in 17 s; lift the cap with the byte scanner and typed arrays from the streaming readers row.

ModelCraftMarkupCraftTakeoffCraft App code Planned 2 – docs/0.9-sessions/perf-wave3/
FixPERF.30 Link to PERF.30Comment on PERF.30 CadCraft: the Problems audit stringifies every entity after every change; DXF parses on the main thread; save ceilings at 1M entities

problems-cadcraft.js:45/52 shapeKey and auditIds cost 450 ms per edit at 100k entities (a JSON key per entity after each change): make the audit incremental over the touched ids. readDxf runs on the main thread (1.9 s at 100k; the DWG reader already has a worker, dwg/read-async.js): move the DXF reader into the same worker or stream it (the scale readers row). Save ceilings: writeDxf2000 dies at 1M entities with "Invalid string length", saveDwg takes 58 s and 4.2 GB of heap at 1M (over Chromium's default) and 6.1 s / 1.4 GB at 100k: chunked Blob output (the writers row) and the DWG writer in a worker (the autosave row, which measured the autosave write at 0.5 s for 10k and 5.8 s for 100k on the main thread).

CadCraft App code Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.31 Link to PERF.31Comment on PERF.31 CivilCraft: Surface.outline() takes 20 to 30 s per call at 1 M triangles and runs again on every zoom bucket (63 % of a 30 s open)

Measured on the 500k-point survey (survey-500k.civilcraft.json and landxml-tin-500k.xml): tin.js:633-648 builds string keys for 3 M directed edges with no memo, and planScene calls outline() on every mmBucket change, so it is 63 % of the 30 s open, 79 % of the scene phase, and wheel frames have a p95 of 13 to 36 s on both files. Change: memoize the outline per surface version and use integer or half-edge keys (about 100 ms once). The biggest single win of the 2D track. Healthy around it: elevationAt 0.25 ms, profiles 4 to 10 ms, Delaunator about 0.3 s at 500k, indexed picking (16 ms sweeps).

CivilCraft App codeOur engines Planned 1 – docs/0.9-sessions/perf-wave3/
FixPERF.33 Link to PERF.33Comment on PERF.33 CivilCraft: opening a project is one 27 to 41 s main-thread task, and every chosen .json is read and parsed whole twice

f.text() then parse then buildSurface then scene run synchronously in one task (27 to 41 s at 500k; LandXML 69 MB becomes a 1.2 GB element tree). Files over 200 MB are refused by design (civilcraft.js:2320), which is honest, but ifc-native-ui.js:112 reads and JSON.parses every chosen .json whole first to sniff it (6.8 s and 751 MB for a 240 MB file that is then refused), so every project is parsed twice; the LandXML reader cannot take the 692 MB 5 M-point file (over V8's largest string) and fromJSON caps at 2 M points. Change: a 4 KB sniff, the parse and surface build in a worker returning typed arrays by transfer, a streaming LandXML reader (the scale readers row), and progress in the status line. The LAS terrain import is the model to follow: 10.6 M points streamed in 2 MB pieces, thinned to 106k, 2.3 s, no long task (wave 2 stopped at the elevation-unit dialog; Import stays disabled until it is answered).

CivilCraft App code Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.34 Link to PERF.34Comment on PERF.34 TakeoffCraft: the snap index is built on the main thread when a sheet opens (7 to 8 s, 394 MB on a 300,000-segment sheet)

snap.js:93 pageLinework plus SnapIndex runs at sheet open with snap on by default: a 7 to 8 s task, 9 s blocked and 394 MB on the dense 300k-segment sheet (MarkupCraft builds its equivalent in 549 ms). The grid itself is right. Change: build it in a worker from the page's operator list, or lazily on the first snap request, with the first sheet view not waiting for it.

TakeoffCraft App code Planned 1 – docs/0.9-sessions/perf-wave3/
FixPERF.35 Link to PERF.35Comment on PERF.35 MarkupCraft and TakeoffCraft: a 2,000-sheet set builds every row and canvas up front, TakeoffCraft rebuilds the list on each switch, heap reaches 1.66 GB

Measured on plan-set-2000.pdf (2,000 sheets, 99 MB; pdf.js opens it in 2.1 s): MarkupCraft renderSheets (markupcraft.js:1404-1426) builds all 2,000 li plus canvases at open and calls store.onSheet(key) per sheet (sheets times markups); TakeoffCraft showSheet (takeoffcraft.js:362-380) rebuilds the entire sheet list with replaceChildren plus a pass over every mark on each switch: switch p50 345 ms and 5.2 s blocked over 20 switches at 2,000 sheets (MarkupCraft 91 ms, 1.9 s); 14,000 DOM nodes and 2,000 canvases at open; heap 855 MB, 1.66 GB after scrolling the thumbnails, 1.49 GB after Find; page.cleanup() is never called. MarkupCraft Find is sequential (73 s for 2,000 sheets, 37 ms a page) and textCache is unbounded. Change: a virtualized sheet list (rows and thumbnails only for what is visible), keyed updates on switch, page.cleanup() for pages out of view, Find in batches with a bounded text cache. Targets: switch p95 under 150 ms, heap under 400 MB. Healthy: a 300k-segment sheet pans and zooms at 16 ms frames, 10,000 markups on one sheet cost 124 ms and keep 16 ms frames, TakeoffCraft 10,000 marks in 29 ms and quantities() in 0.8 ms.

MarkupCraftTakeoffCraft App code Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.36 Link to PERF.36Comment on PERF.36 DetailCraft: every edit recomputes everything: autopilot, a quadratic clash check, a full scene of 1,925 draw calls, two JSON snapshots, linear model scans

Measured in /use 0.8.1 and in Node (tools/perf/apps3d-detailcraft.mjs, apps3d-detail-node.mjs; 2,820-member grid24x12x5 and the new 10x grid48x30x10 with 28,500 members and 100x grid60x60x40 with 285,600 members): refresh() (detailcraft.js:354) after any change runs build 151 ms, markParts 261 ms, autopilot.propose 899 ms (18 times more for 6.2 times the members), clashCheck 10.2 s (quadratic), and rebuilds the whole three.js scene with a Mesh plus an EdgesGeometry per part (1,925 draw calls for 948 parts); Model.change stringifies the state twice (model.js:165/170; 20 MB at 10x, 197 MB at 100x) and undo re-parses all of it; Tx.addHole and addConnection find by find/some (model.js:349, :376), so pasting the 10x model took 2.6 s and building the 100x file 716 s. Change: incremental build by id, patch-based undo (the whole-document snapshot row), an instanced or merged scene, id maps in the model API, clash check through a grid. Healthy: build, marks, drawings and IFC export are near linear.

DetailCraft App code Planned 6 FX.144 (the constructability hang) first, or nothing above 456 members can be measured in the browser. docs/0.9-sessions/perf-wave3/
FixPERF.37 Link to PERF.37Comment on PERF.37 BimCraft: the first click builds the inverse index on the page thread (5 to 12 s), picking raycasts every triangle, a section plane rebuilds all caps

Measured in /use 0.8.1 on 22.5 / 115 / 201 MB IFC (tools/perf/apps3d-bimcraft.mjs): the first property lookup calls ifc/model.js:173 inverse() (api.js:347-421) over the whole file: one 5.1 s task at 115 MB, 7.7 s plus properties = 12.1 s blocked at 201 MB. hitAt (viewer3d.js:833) has no bounding-volume tree although three-mesh-bvh is vendored: 47 to 133 ms per raycast at 683k triangles, 180 to 508 ms at 2.26 M, on every hover. setSection rebuilds every batch's stencil caps (updateCaps viewer3d.js:504): 1.4 s, 3.9 s, 8.0 s. Change: build the inverse index in the scan worker (open-async.js) or in idle slices after open; computeBoundsTree per batch; cache per-batch cap geometry and cull batches by the plane. Open itself: blocking 9.0, 13.9, 18.5 s with longest tasks 3.0, 5.5, 8.2 s (StreamAllMeshes api.js:312 in one synchronous loop, appendGeometry ifc-model.js:238 into JS arrays, runEdges ifc-vis.js:111 at 1.1 s per million triangles, the property index put at open): that is PERF.8, now with 10x numbers. Healthy: batching (46 to 104 draw calls), render CPU 2 to 7 ms, the tree panel (19k nodes, setTree 12 ms), idle after open.

BimCraft App codeOur engines Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.38 Link to PERF.38Comment on PERF.38 ModelCraft: edits, undo, exports, autosave and the guide card do whole-model work on the page thread (500k faces: move 3.3 s, undo 2.7 s, OBJ export 12.8 s)

Measured in /use 0.8.1 with m3d-500k-faces (tools/perf/apps3d-modelcraft.mjs): import 9.7 s (9.1 s blocking, one 5.3 s task; 50k is 3.1 s), heap 992 MB (about 2 KB per face; the 5 M-face GLB crashed the renderer tab, the 177 MB OBJ is refused by MAX_TEXT after decoding it); moving a placement 271 ms (GLB) or 3.3 s (OBJ, because every edge is drawn sharp without normals: 757k edge lines), undo 2.7 s (restored definitions rebuild their geometry), Model.change calls snap() twice with meshes as base64 (model.js:2408, mesh.js:751/1029; the snapshot row); the guide card's shells() costs 1.9 s = 16 % of the import, polled by setInterval(syncGuide, 400) (modelcraft.js:3151, firstmodel.js:29), and the MeshDef.faces getter (model.js:98) converts the whole mesh on every read; exports block the page: GLB 8.5 s, OBJ 12.8 s, DAE 3.2 s, the file text 2.7 s / 43 MB, and autosave shows as a 2.6 s long task in idle. Change: writers and snapshots in a worker, the guide card from cached counts, faces as typed arrays, OBJ normals computed once so edges are not all sharp. Healthy: picking and hover 0 ms (BoxTree; the MeshPicker tree builds in 336 ms), 90 draw calls, linear import.

ModelCraft App code Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.39 Link to PERF.39Comment on PERF.39 PartCraft: a rebuild is one synchronous task (750 features 3.0 s, a 40 by 40 hole plate 8.9 s, every edit 1.3 to 1.6 s) and the D7.51 checkpoints save nothing

Measured in /use 0.8.1 (tools/perf/apps3d-partcraft.mjs): rebuilds are linear (4 to 8 ms per feature, 5.6 ms per hole: 50 features 0.32 s, 200 1.6 s, 750 3.0 s; plates 10x10 0.43 s, 20x20 1.5 s where the 0.5 JS died, 30x30 3.7 s, 40x40 8.9 s), but rebuild() (partcraft.js:351 into the manifold wasm) runs on the page thread as one task, so the app freezes for the whole of it, and an edit of the LAST feature with the checkpoint costs 1,475 ms against 1,352 ms for a full rebuild because prefixKeys (kernel.js:814) hashes every feature's JSON per rebuild. Change: the mesh kernel in a worker like brep-worker.js with progress and cancel, cheap prefix hashing (a per-feature hash kept on the feature), undo and redo (1.6 and 1.3 s today) from cached results. Healthy: orbit at 15 draw calls, the Parameters window 5 ms.

PartCraft App codeOur engines Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.40 Link to PERF.40Comment on PERF.40 PartCraft: assembly endFrame finds every joint's instances by a linear search (O(n squared): 0.3 s at 20,000 instances, about 30 s at 200,000)

assembly.js:201 looks up instances with find per joint inside endFrame, so placing all is quadratic: placeAll 297 ms at 20k instances in the lab build (open plus build plus place 0.43 s, 3 draw calls thanks to the 0.9 instancing, pick 7 ms) and about 30 s at 200k by the square (the lab run of assembly-200000.partcraft-asm.json did not complete in time; the 0.8.1 release does not open 0.9 assembly files and draws a Mesh, a LineSegments and a new material per instance, view3d.js:395-425). Change: a Map from id to instance built once per frame or kept on the assembly.

PartCraft App code Planned 1 – docs/0.9-sessions/perf-wave3/
FixPERF.51 Link to PERF.51Comment on PERF.51 Apps: 56 to 69 % of the JavaScript fetched at start never runs; dynamic import boundaries per command, workspace and file format (extends PERF.4)

Measured with V8 precise coverage on the current releases (tools/perf/addons-coverage.mjs): 6.4 MB (CadCraft) to 10.2 MB (BimCraft) of decoded JS per start, 363 to 492 modules, and after 10 s of idle plus a first command only 31 to 44 % of those bytes have executed (CadCraft 33 %, PartCraft 35 %, DetailCraft 44 %); interaction adds 1 to 2 %. Every file of the entry's static import closure is fetched (284/284 in CadCraft, 432/432 BimCraft). Per app 23 to 33 modules ran under 15 %: 0.3 to 1.1 MB each app, for example the whole DWG/DXF save path in CadCraft (dxf.js 95 KB at 8 %, dwg/write.js 58 KB at 8 %, dxf2000.js, lisp/*), ModelCraft io.js 106 KB at 11 %, PartCraft ws-illustrate 96 KB, sketchview 79 KB, ws-script 66 KB, BimCraft res-ui 107 KB, CivilCraft eg-ui 47 KB and the river/rail/water UIs. modulepreload (PERF.4) hides latency but does not cut bytes. Change: import() boundaries at the command, workspace and file-format level (the command registry already knows each command's module, input-<slug>.js); start with the entry, the view, the model and the toolbar; keep modulepreload for what stays static. Target: 1.5 to 3 MB less to download, parse and compile before the first drawing.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App code Planned 10 – docs/0.9-sessions/perf-wave3/
FixPERF.57 Link to PERF.57Comment on PERF.57 Apps: start-up work for panels nobody opened: BimCraft massing 191 ms, ModelCraft swatches 182 ms plus 650 ms idle tasks, CivilCraft plants seed, exr table

BimCraft massingOptions() runs 191 ms inside mountProgram (design-ui.js:77); ModelCraft paints a swatch per library material from 128 px procedural textures at start (modelcraft.js:557, :581; 182 ms) and is the only app with long tasks during idle (650 ms); CivilCraft fetches and cleans plants-seed.json (198 KB) at start (landscape-ui.js:117); exr/util.js builds a 65,536-entry half-float table at import (exr/util.js:38-46); three.module.js top level is 65 to 94 ms. Change: compute on first open of the panel or first paint of the palette (IntersectionObserver or the tab event), fetch plants on first Landscape use, build the half-float table lazily. 0.2 to 0.4 s of blocking per start in the 3D apps.

BimCraftModelCraftCivilCraft App code Planned 2 – docs/0.9-sessions/perf-wave3/
FixPERF.71 Link to PERF.71Comment on PERF.71 Code: eight app mains are one closure of 2,500 to 5,800 lines each; split by topic

The AST pass over 5,500 modules (204,968 functions) found each app's main a single init closure: bimcraft init 5,759 lines with 1,557 nested functions, markupcraft 4,755, takeoffcraft 4,509, modelcraft 3,858, partcraft 3,770, cadcraft 2,898, civilcraft 2,848, detailcraft start 2,478. Everything inside shares one scope, so nothing can be dynamically imported (the start-up row), tested alone or reasoned about; this is the single biggest piece of sloppy structure the owner asked about. The report (docs/0.9-sessions/perf-wave3/code-quality.md Q1) outlines each by its inner functions and proposes modules per topic (BimCraft: IFC open/save, fd, QTO, find, clash, props, nine context modules; the same per app). L per app, M for CadCraft; one app per lab pass, behind the existing browser tests (tools/<app>-test, bin/test-<app>*.mjs).

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App code Planned 20 – docs/0.9-sessions/perf-wave3/
FixPERF.72 Link to PERF.72Comment on PERF.72 Code: 4,300 one-line functions that only pass arguments on (forwarder adapters rebuilt per sub-UI, help: () => t() closures)

tools/perf/quality-forwarders.mjs: 2,299 forwarders plus 2,007 thin wrappers, 278 getters, 401 constant-return functions, 2,683 one-use helpers. Worst: civilcraft.js 182 forwarders (units: () => units(), say: (m) => say(m), row: (...x) => row(...x) rebuilt for four sub-UIs, lines 157-165), bimcraft.js 143, partcraft.js 117, landscape-ui.js 52 (the same {edit, LS, U} trio six times), commands.js 147 help: () => t('…') closures plus seven ask wrappers. This is read and parse cost and noise, not runtime (inlined). Change: pass the declarations by name, one adapter object per app shared by its sub-UIs, help: 'key' resolved at display time. S per file.

CadCraftBimCraftCivilCraftDetailCraftPartCraft App code Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.73 Link to PERF.73Comment on PERF.73 Code: copies that drifted and hand copies with no sync tool (six inflate implementations, xlsx.js in four variants, input-mouse.js in eight apps)

The byte-copy system works where a tool owns the copy (1,011 of 1,043 copy groups). Outside it: inflateRaw plus deflateZlib byte-identical in lib/3dm and lib/chip, six inflate implementations in lib and crc32 in 316 files (one lib/zip); xlsx.js six times in four hand-edited variants (one lib/xlsx); pdf-snap.js and takeoffcraft snap.js diverged; lib/walls ahead of bimcraft wall-square.js (two solvers for one wall); 31 drifted file pairs in all; whole-file hand copies with no tool: input-mouse.js in eight apps, wheel-pad.js in five, panes.js in three, params.js, framing.js, bcf.js, render-tools.js, carbon-factors.js, keynotes.js; CadCraft's m3d copy stale because its tool refuses to run (FX.143). Change: one owner per copy (lib/ or the shell) with a sync tool, and bin/test-sync-copies.mjs that runs every sync tool with --check (130 tools; some write without the flag: give them one first). Also the STEP string helpers S/E/L/B typed into five IFC writers (civilcraft three, detailcraft, partcraft) belong in lib/ifc/step.js.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App codeTests and tooling Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.74 Link to PERF.74Comment on PERF.74 Code: shared utilities written hundreds of times: vector math 2,088 implementations, csv cell 431, point-in-polygon 243, esc 198, uid 188, download 163

tools/perf/quality-utils.mjs counted 6,943 implementations of 24 utility names (21,260 lines): vector math 2,088 (516 distinct bodies in 1,094 files), area 299, point-in-polygon 243 (identical clusters of 17, 12, 9, 8 and 6), bbox 190, lerp 69, clamp 68; text and bytes: esc 198, uid 188, download/saveBlob 163, csv cell 431, base64 59. The shell exports none of these; lib/units is the one shared table that is used everywhere. Change: lib/geom2d (vec, bbox, area, point-in-polygon, lerp, clamp) and a shell util.js (esc, uid, download, csv, base64) synced into the builds like host.js; mechanical replacement guided by the cluster list, with the quality scripts kept as the check so the count only goes down. Also shell-mount.js (a 48-line block identical in seven apps) and ui-util.js (eight variants of one 45 to 76 line file) become synced shell files, and the Studio Live stateCore plus diff and invert hand-copied in three apps and joinFromPage in six become a shell live-state.js.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App codeApp shell Planned 6 – docs/0.9-sessions/perf-wave3/
FixPERF.75 Link to PERF.75Comment on PERF.75 Code: deep clones by JSON round trip (205 sites, some inside merge loops) and 24 timers polling faster than once a second

Perf-relevant sloppiness confirmed by reading the top sites. Clones: 205 JSON.parse(JSON.stringify()) plus 112 clone helpers; takeoffcraft/a Discord member/learn.js clones inside merge loops (11 sites), clipboard.js and dwg-keep.js clone document-sized objects: structuredClone or a cloneDoc that copies only what changes. Timers: cadcraft live.js:763 and markupcraft live-ui.js:71 setInterval(push, 33) build presence and JSON.stringify it 30 times a second while idle; hazard-ui and people-ui animate with setInterval 33; countall-ui waits on a worker with a 20 ms poll; plc-ui 50 ms; 250 to 500 ms pollers elsewhere. Change: event-driven pushes with a dirty flag, requestAnimationFrame for animation, promise-based worker replies; a check that lists any setInterval under 1 s. Also: a console.log in bimcraft fem/frame-push.js:155 inside the solver loop, 12 new RegExp inside loops (takeoffcraft xlsx-read.js:74 on file open).

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App code Planned 2 – docs/0.9-sessions/perf-wave3/
FixPERF.76 Link to PERF.76Comment on PERF.76 Code: O(n squared) searches in kernel code (ModelCraft mesh ops, the shared m3d model, CivilCraft LandXML) and render functions that rebuild whole panels

Nested-loop searches with includes/some inside N by N loops: modelcraft mesh/ops.js:421 subdivide (also :471, :820, :915), m3d model.js:693, :778, :1637, :1957 (in both ModelCraft and CadCraft's copy), civilcraft landxml.js:298-322. These are the places a 10x model turns into a 100x wait; the 3D track's numbers (ModelCraft push/pull and undo at 500k faces) land here. Change: Sets and Maps keyed by vertex or id, measured before and after with tools/perf/scale-bench.mjs and the apps3d scripts. Render functions that rebuild and re-query the DOM on every call (196 querySelectorAll and 315 addEventListener inside render functions; takeoffcraft.js sheet list on every sheet switch, shell app-workspace.js, supercadcraft.js, cadcraft renderLayers and renderSelTotals): keyed children and incremental updates, measured on the 2D track's 2,000-sheet set.

CadCraftModelCraftTakeoffCraftCivilCraft App codeOur engines Planned 4 – docs/0.9-sessions/perf-wave3/
FixPERF.78 Link to PERF.78Comment on PERF.78 Code: dead modules and exports, god functions and cyclomatic hotspots beyond the mains

Confirmed dead: cadcraft addons/windows/lib/cutlist/cutlist.js (190 lines, a stale copy nothing imports), addons/trusscraft/lib/layout.js in four apps (tests only), modelcraft anim/skin-gpu.js, compute/parity.js twice, partcraft eng-data and eng-units stubs; 32 dead exports (about 54 KB: partcraft worker solvers, the openfoam runner) plus 26 orphan modules (about 141 KB). Name-level dead code across the 5,020 namespace and dynamic imports needs a bundler dry run (one tree-shake pass per app as a report, nothing shipped bundled). Hotspots: 120 functions over 300 lines and 618 with cyclomatic complexity 40 or more: readFamily 234, writeDwg 769 lines at 214 (plus convert 195, buildModelParts 182), vla-host get 207, validateItem 194, buildSite3D 190, dxf entityFrom0 187 and writeDxf 159, pdf-write writeMarkedUp 182, writeCivilIfc 702 lines, model-ui mountModel 1,804 lines, res-ui 1,407, shell app-panels 1,742 and app-settings 1,521. Change: table-driven dispatch for the entity switches, one module per panel section, delete the dead files; each behind the existing tests.

CadCraftBimCraftModelCraftCivilCraftDetailCraftPartCraft App code Planned 4 – docs/0.9-sessions/perf-wave3/
Suggest an item

Anyone can send one; no account and no name needed. The team reads each idea, and the ones we take on appear on this page. Please leave out web addresses.