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 2 12
FixPERF.7 Link to PERF.7Comment on PERF.7 ModelCraft: importing a 10.7 MB glTF freezes the page for 8 s and peaks at 932 MB of JS heap

Measured 2026-11-08 in /use with tools/perf/open-file.mjs (Chromium, software GL): MetalRoughSpheres.glb (10.7 MB, glTF sample assets) through the Import input: open 8.5 s, 8.2 s of it main-thread long tasks (6 tasks, the longest 3.0 s: nothing on the page answers), JS heap 22 MB to 932 MB at the peak, 413 MB once settled. During zoom right after, frames p95 649 ms (software GL; a graphics card will do better, the main-thread stalls will not). Where: io.js / modelcraft.js parse and build the scene on the page thread. Plan: parse glTF/OBJ/STL/PLY in a worker (the DWG reader already has read-async.js as the pattern), transfer the typed arrays, build meshes in batches with a yield between, free the decoded copies (keep one copy of each buffer: the GPU upload, not the parsed JSON + arrays + three.js clones). Target: no long task over 200 ms, heap peak under 3x the file. Wave 3 (apps-3d.md): 500k faces import in 9.7 s with 9.1 s blocking and 992 MB of heap (about 2 KB per face); the 5 M-face GLB (114 MB) crashed the renderer tab; the first edit after a 400 MB import costs 36 s and 9 GB in Node because of the whole-model snapshot (PERF wave 3 rows).

ModelCraft Planned – –
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.22 Link to PERF.22Comment on PERF.22 Scale: cc-dwg keeps 35 bytes of heap per file byte, clones the drawing out of the worker, and the writer is super-linear (extends PERF.5, PERF.9, PERF.10)

Measured at 10x (a 76 MB R2004 drawing, 1.4 M entities): readDwg 19.9 s and 2.6 GB after read (one object per entity and per vertex, objects-read.js:608-613 and :1105), the worker reply structuredClone adds 1.06 GB and 14.6 s (read-async.js:67), writeDwg 66 s (33 s from a fresh model), exponent 1.3. A 760 MB drawing would need about 27 GB: it does not fit anywhere. Browser ceiling today about 55 to 110 MB. Change: a struct-of-arrays entity store with lazy per-entity decode from the object map (the map and section locators are small; readDwg(bytes) at read.js:30 becomes openDwg(blob) with byte-range decoding), the worker reply as transferable typed buffers (or decode on the page from the transferred bytes), and a writer that streams sections to a FileSystemWritableFileStream. Reader L, writer M. The four apps carry byte copies of lib/dwg.

CadCraftBimCraftModelCraftCivilCraft Our engines Planned 12 – 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.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.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.52 Link to PERF.52Comment on PERF.52 Apps: the add-on loader is serial and imports every add-on's help at activation: 13 add-ons on = 235 requests, 3.0 MB, 2.5 s of steps

addons.js (the shell copy synced into every build) awaits each bundled addon.json in turn (:390-396; 13 in CadCraft, default-off ones too), activates one after another (:381) and imports each add-on's help module at activation (:572-589; Electrical's 58 KB every start). Measured with 13 add-ons on (tools/perf/addons-load.mjs): 235 requests, 3.0 MB, 2,552 ms of serial steps, longest step 382 ms; in parallel the same work is about 0.4 s. Baseline CadCraft with nothing on: 13 serial addon.json round trips, about 0.9 s on a real line. Change: put every bundled manifest into addons/index.json at release time (one request) or Promise.all the discovery, import all entries in parallel and activate() in index order, import help lazily from api.help on F1.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App shell Planned 3 – docs/0.9-sessions/perf-wave3/
PlatformPERF.53 Link to PERF.53Comment on PERF.53 Apps: 29.6 MB of byte-identical files under different URLs across the eight releases, and every new release re-downloads 98 % unchanged files

tools/perf/addons-shared.mjs hashed every file of the eight current releases: 665 distinct contents under 2,163 URLs, 29.6 MB extra on the wire for a person who opens more than one app (cabinetcraft add-on in four apps 5.8 MB, dwg/ 3.0 MB, ifc schemas in four apps 2.3 MB, regs in four 2.3 MB, m3d 1.3 MB, crs-db.json 914 KB three times, host.js / addons.js / libitem.js eight times). Across releases: CadCraft 0.8.1 → 0.8.2 kept 2,084 of 2,127 files byte-identical (158 MB) and every one is re-downloaded under the new version URL; the same for every app and every "Save for offline" re-save (PERF.15). Vendor libraries are fine (one URL each, pinCurrent works). Change: a shared /assets/lib/<name>/<content-hash>/ URL for lib/* copies and shared add-ons (release-app.php pins it like vendor paths; the import map aliases ./dwg/x.js to it), or content-hashed file names so an unchanged file keeps its URL across releases. Both shrink the import map, which lists every file because ?v= busting needs an entry per module (499 entries / 48 KB in CadCraft, 772 / 78 KB in BimCraft per page view; AppBuilds.php:391-397).

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft Site and servicesApp shell Planned 8 – docs/0.9-sessions/perf-wave3/
FixPERF.55 Link to PERF.55Comment on PERF.55 Apps: 84 to 95 % of the language keys shipped on an app page are never read; split the catalog by namespace (extends PERF.2)

Instrumented t() on the current releases: CadCraft ships 8,818 keys / 529 KB in #cc-lang, 442 are read by the end of idle and 1,024 after a first command; 70 namespaces (198 KB) are never touched in the first minute (frame 32 KB, m3d 29 KB …); BimCraft 117 namespaces / 300 KB untouched; PartCraft's script namespace 1,140 keys / 73 KB with 13 read; the shell's 1,515 keys / 82 KB with about 300 read in every app. Parsing is cheap (5 to 11 ms); the bytes are the cost, 400 to 500 KB of every page view. Change: split the catalog by namespace at build time (tools/lang), inline shell plus the entry's namespaces, load a namespace on the first t() miss or together with the module that owns it (the dynamic-import row gives the boundary); Lang::merged() key prefix is the split key.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft Help and wordsApp shell Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.56 Link to PERF.56Comment on PERF.56 Apps: the shell costs 154 to 257 ms of main thread per start, and three document-wide MutationObservers fire 100+ times a second while drawing

Measured per app: app-phone.js 17 to 56 ms running the phone layout on a desktop viewport; app-boot.js 10 to 73 ms with a body-subtree MutationObserver whose say() rewrites the DOM on every mutation (app-boot.js:82-83) and never settles while the pointer moves (141 to 221 boot-step rewrites in 3 s of drawing); three observers cascade (app-shell.js:477-482, app-phone.js:421 and :483; CadCraft 360 observer callbacks in 3 s of drawing); app-settings.js 15 to 65 ms; app-devices-ui.js 26 ms. Shell bytes: 1.35 MB fetched, about 780 KB never run (app-addons-account.js 59 KB when signed out, calc.js, ai-*.js, app-devices-ui.js, app-propgrid.js). Change: app-phone returns after the matchMedia check on wide pointer-fine screens and attaches its observers only when cc-phone is on; app-boot ignores mutations inside output-like elements and disconnects on settle; app-shell observes dialog additions only (or an attributeFilter without childList on the whole tree); dynamic-import addons-account, calc, ai-*, devices-ui on first use.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App shell Planned 3 – 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/
PlatformPERF.58 Link to PERF.58Comment on PERF.58 Apps: an installed add-on package is relinked from blob modules on every start (hvac 334 ms, plant 756 ms) and ships as base64 JSON

addons.js linkPackage turns each verified package into blob: module URLs every start: hvac 0.9.0 (193 files, 1.8 MB; 2.4 MB as base64 JSON) costs 334 ms per start (link 115, import 55, verify 6); plant 756 ms (import 447). The Ed25519 plus sha256 check is cheap (6 to 11 ms for 100 to 193 files); blob modules get no HTTP cache and no V8 code cache, so the cost repeats on every visit and grows with file count. Change: keep the signature check but serve verified packages from the service worker under a stable /addons/pkg/<sha>/<path> scope (or cache the linked texts and import() a persistent URL) so modules get code caching; stop base64 in the package JSON (binary zip, about 34 % smaller). AddonPackages.php, addons.js, sw.js.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App shellSite and services Planned 3 – docs/0.9-sessions/perf-wave3/
PlatformPERF.59 Link to PERF.59Comment on PERF.59 Apps: no service worker until someone clicks "Save for offline"; every repeat visit is a cold start minus the 7-day HTTP cache

app-offline.js:160 registers /sw.js only on the click, and sw.js:46 installs nothing on its own; the PWA manifest promises more than the page delivers. Every visit refetches the 830 to 940 KB app page (PERF.2, PERF.3) and relies on the HTTP cache for the 300 to 500 module files (which Vary: Referer defeats, see the server rows). Change: register /sw.js on every /use page; cache-first for immutable URLs (?v= and pinned vendor/app folders: the worker already knows the rule, sw.js:33-40), stale-while-revalidate for the page; "Save for offline" stays the explicit full copy. Pairs with the shared-URL row (a shared lib URL is cached once for all apps) and the installed-package row.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft App shell Planned 3 – docs/0.9-sessions/perf-wave3/
FixPERF.60 Link to PERF.60Comment on PERF.60 Apps: the help index is one 1.9 MB module imported on F1 (52 locale copies = most of the 103 MB offline help; extends PERF.15)

CadCraft help/help.js is 1.9 MB (BimCraft 1.7 MB), parsed whole on the first F1; the 52 compiled locale copies at 2.1 to 2.2 MB each are most of the 103 MB help folder that "Save for offline" downloads. Change: compile help as an index (titles, search terms) plus one chunk per topic group, load a topic on open; offline saves the index for every language and topics for the chosen one. bin/build-help.php, help-panel.js.

CadCraftBimCraftModelCraftMarkupCraftTakeoffCraftCivilCraftDetailCraftPartCraft Help and words Planned 3 – 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.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.