Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5a436cf
Merge pull request #30 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 22, 2026
f365b77
Merge pull request #32 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
b332c40
Merge pull request #33 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
5a0ed8e
Merge pull request #34 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
585428e
Merge pull request #35 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
d9f26a8
Merge pull request #36 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
75b4d13
Merge pull request #37 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
92d86cf
Merge pull request #38 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
17707d8
Merge pull request #39 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
0839f3a
Merge pull request #40 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
fb11f88
Merge pull request #41 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
7e3a1b5
Merge pull request #42 from thomhoffer-arch/claude/fix-ifc-security-b…
thomhoffer-arch Mar 23, 2026
3f24cb9
Merge pull request #43 from thomhoffer-arch/main
thomhoffer-arch Mar 23, 2026
03817f9
Merge pull request #44 from thomhoffer-arch/claude/debug-clash-contro…
thomhoffer-arch Mar 23, 2026
f0e9195
Fix 7 bugs found in review: stale camera, material leak, dead code, U…
claude Mar 23, 2026
b6af756
Merge remote branch and apply bug fixes
claude Mar 23, 2026
c54517c
Merge pull request #45 from thomhoffer-arch/claude/debug-clash-contro…
thomhoffer-arch Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 4 additions & 59 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,10 @@
# Changelog

## v1.2.45 (2026-03-23)
- Add project switcher to left sidebar
## v1.2.26 (2026-03-23)
- Fix 7 bugs found in review: stale camera, material leak, dead code, URL leaks

## v1.2.44 (2026-03-23)
- Fix duplicate models on tab switch; improve clash panel UX

## v1.2.43 (2026-03-23)
- Fix hooks-after-return in SidePanel causing blackout on tab open

## v1.2.42 (2026-03-23)
- Fix view cube 3D depth, orientation stability, IFC/2D buttons, resizable panel

## v1.2.41 (2026-03-23)
- Replace folder icon in left rail with IFC and 2D label buttons

## v1.2.40 (2026-03-23)
- Split upload button into IFC and 2D buttons

## v1.2.39 (2026-03-23)
- Fix tooltips rendering behind the 3D viewer

## v1.2.38 (2026-03-23)
- Fix viewcube fly-to and improve visual style

## v1.2.37 (2026-03-23)
- Split icon rail into left (utility) and right (nav tabs) rails

## v1.2.36 (2026-03-23)
- Replace text labels with animated hover tooltips on sidebar icons

## v1.2.35 (2026-03-23)
- Add folder-with-plus SVG icon to file upload button and empty state

## v1.2.34 (2026-03-23)
- Redesign UI: vertical icon sidebar + slide-out panel, maximized viewer

## v1.2.33 (2026-03-23)
- Tone down LLM error banner; improve welcome message

## v1.2.32 (2026-03-23)
- Add animated loading border + status indicator to NL panel

## v1.2.31 (2026-03-23)
- Significantly broaden NL panel regex parsing

## v1.2.30 (2026-03-23)
- Fix LLM model load: q4f16→q4, add error details and retry button

## v1.2.29 (2026-03-23)
- Add in-browser LLM to NL command panel (Qwen2.5-0.5B via Transformers.js)

## v1.2.27 (2026-03-22)
- Restore element ghost effect on viewer click

## v1.2.26 (2026-03-22)
- Restore element ghost effect on viewer click

## v1.2.25 (2026-03-22)
- Add natural language command panel (ClashControl AI)
- Refocus NL panel on clash setup, Q&A and workflow — not spatial interactions
## v1.2.25 (2026-03-23)
- Simplify: remove dRef, fix img cache pattern, deduplicate hasProjectData, fix unmount leak

## v1.2.24 (2026-03-22)
- Fix 8 code review issues: CSP, SRI, state mutation, stale closures, dead code
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ClashControl
> Version: **v1.2.45** (2026-03-23)
> Version: **v1.2.26** (2026-03-23)

**Free, open-source IFC clash detection — right in your browser.**

Expand Down
24 changes: 13 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@

function startApp() {
// ── Version (auto-updated by pre-commit hook) ──
var CC_VERSION = {v:'1.2.45',hash:'85465b6',date:'2026-03-23T10:52:36Z'};
var CC_VERSION = {v:'1.2.26',hash:'f0e9195',date:'2026-03-23T11:10:00Z'};

// Check CDN deps loaded
var need = [];
Expand Down Expand Up @@ -307,7 +307,6 @@
case 'SET_PROJECT_LIST': return Object.assign({},s,{projectList:a.v});
case 'LOAD_PROJECT_STATE': return Object.assign({},s,a.data);
case 'CLASH_GROUP_BY': return Object.assign({},s,{clashGroupBy:a.v});
case 'TOGGLE_PROJECTION': return Object.assign({},s,{projection:s.projection==='ortho'?'perspective':'ortho'});
// 2D Underlays
case 'ADD_UNDERLAY': return Object.assign({},s,{underlays:s.underlays.concat([a.v]),activeUnderlay:a.v.id});
case 'DEL_UNDERLAY': return Object.assign({},s,{underlays:s.underlays.filter(function(u){return u.id!==a.id}),activeUnderlay:s.activeUnderlay===a.id?null:s.activeUnderlay});
Expand Down Expand Up @@ -974,6 +973,7 @@
a.href = URL.createObjectURL(blob);
a.download = 'project-'+new Date().toISOString().slice(0,10)+'.json';
a.click();
URL.revokeObjectURL(a.href);
}

function loadProject(d) {
Expand Down Expand Up @@ -1191,6 +1191,7 @@
a.href = URL.createObjectURL(blob);
a.download = 'clashcontrol-'+versionId+'-'+new Date().toISOString().slice(0,10)+'.bcf';
a.click();
URL.revokeObjectURL(a.href);
});
}

Expand Down Expand Up @@ -1283,7 +1284,6 @@
},{passive:false});
var orbit = {target:target, sync:sync, apply:apply, sph:sph, camera:camera};
// Override apply to use orbit.camera (supports projection switching)
var _origApply = apply;
orbit.apply = function() {
camera = orbit.camera; // keep local camera in sync
sph.phi = Math.max(0.05, Math.min(Math.PI-0.05, sph.phi));
Expand Down Expand Up @@ -1570,6 +1570,7 @@
// Vertical ring for 3D visibility
var ring3 = new THREE.Mesh(ringGeo2, ringMat2.clone());
ring3.position.set(point[0], point[1], point[2]);
ring3.rotation.y = Math.PI / 2;
grp.add(ring3);
scene.add(grp);
_activeClashMarker = grp;
Expand Down Expand Up @@ -1777,7 +1778,7 @@
var rect = renderer.domElement.getBoundingClientRect();
hoverMouse.x = ((e.clientX-rect.left)/rect.width)*2-1;
hoverMouse.y = -((e.clientY-rect.top)/rect.height)*2+1;
hoverRaycaster.setFromCamera(hoverMouse, camera);
hoverRaycaster.setFromCamera(hoverMouse, S.camera);
var hits = hoverRaycaster.intersectObjects(S.mg.children, true);
var hitMesh = null;
if(hits.length>0 && hits[0].object.isMesh) hitMesh = hits[0].object;
Expand Down Expand Up @@ -1833,7 +1834,7 @@
var rect=renderer.domElement.getBoundingClientRect();
var mx=((e.clientX-rect.left)/rect.width)*2-1;
var my=-((e.clientY-rect.top)/rect.height)*2+1;
raycaster.setFromCamera(new THREE.Vector2(mx,my), camera);
raycaster.setFromCamera(new THREE.Vector2(mx,my), S2.camera);
// Check rotation ring first
var allHandles=[];
if(S2._sboxRotRing) allHandles.push(S2._sboxRotRing);
Expand Down Expand Up @@ -1870,7 +1871,7 @@
var rect=renderer.domElement.getBoundingClientRect();
var mx=((e.clientX-rect.left)/rect.width)*2-1;
var my=-((e.clientY-rect.top)/rect.height)*2+1;
raycaster.setFromCamera(new THREE.Vector2(mx,my), camera);
raycaster.setFromCamera(new THREE.Vector2(mx,my), state3d.current.camera);

if(ds.type==='rotate'){
// Rotation: horizontal mouse movement maps to Y rotation
Expand Down Expand Up @@ -1914,7 +1915,7 @@
var rect=renderer.domElement.getBoundingClientRect();
var mx=((e.clientX-rect.left)/rect.width)*2-1;
var my=-((e.clientY-rect.top)/rect.height)*2+1;
raycaster.setFromCamera(new THREE.Vector2(mx,my), camera);
raycaster.setFromCamera(new THREE.Vector2(mx,my), S2.camera);
var allH=[];
if(S2._sboxRotRing) allH.push(S2._sboxRotRing);
if(S2._sboxHandles) allH=allH.concat(S2._sboxHandles);
Expand Down Expand Up @@ -1943,7 +1944,7 @@
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((e.clientX-rect.left)/rect.width)*2-1;
mouse.y = -((e.clientY-rect.top)/rect.height)*2+1;
raycaster.setFromCamera(mouse, camera);
raycaster.setFromCamera(mouse, S.camera);
if(!S.mg) return;
// Filter out ghosted meshes — clicking them would corrupt shared ghost material
var hits = raycaster.intersectObjects(S.mg.children, true).filter(function(h){
Expand Down Expand Up @@ -2101,7 +2102,7 @@
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((e.clientX-rect.left)/rect.width)*2-1;
mouse.y = -((e.clientY-rect.top)/rect.height)*2+1;
raycaster.setFromCamera(mouse, camera);
raycaster.setFromCamera(mouse, S.camera);
var hits = raycaster.intersectObjects(S.mg.children, true).filter(function(h){
return h.object.material !== _ghostMat;
});
Expand Down Expand Up @@ -2711,6 +2712,8 @@
var orig = obj.userData._origMaterial;
// Skip if currently ghosted
if(obj.material === S._ghostMat) return;
// Dispose the current non-original material before replacing to prevent leaks
if(obj.material !== orig) { obj.material.dispose(); }
if(style==='wireframe'){
obj.material = new THREE.MeshBasicMaterial({
color: orig.color ? orig.color.clone() : new THREE.Color(0x64748b),
Expand All @@ -2730,7 +2733,6 @@
side: THREE.DoubleSide, envMapIntensity:0.5
});
} else {
// standard: restore original
obj.material = orig;
}
});
Expand Down Expand Up @@ -4622,7 +4624,7 @@
var defaultTitle = hasEl ? ('Issue: '+(ep.ifcType||'Element')+' '+(ep.name||'#'+el.expressId)) : '';
var defaultDesc = hasEl ? ('Model: '+el.modelName+'\nType: '+(ep.ifcType||'Unknown')+'\nName: '+(ep.name||'N/A')+'\nStorey: '+(ep.storey||'N/A')+'\nMaterial: '+(ep.material||'N/A')+'\nExpress ID: #'+el.expressId) : '';

var f=useState({title:defaultTitle,desc:'',priority:pr.defaultPriority||'normal',status:pr.defaultStatus||'open',category:pr.defaultCategory||'design',assignee:'',dueDate:''}), form=f[0], setForm=f[1];
var f=useState({title:defaultTitle,desc:defaultDesc,priority:pr.defaultPriority||'normal',status:pr.defaultStatus||'open',category:pr.defaultCategory||'design',assignee:'',dueDate:''}), form=f[0], setForm=f[1];
useEffect(function(){
function onKey(e){if(e.key==='Escape')d({t:'MODAL',v:false});}
window.addEventListener('keydown',onKey);
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"major": 1,
"minor": 2,
"patch": 45,
"patch": 26,
"label": ""
}
Loading