@cluesmith/codev 1.5.13 → 1.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cluesmith/codev",
3
- "version": "1.5.13",
3
+ "version": "1.5.14",
4
4
  "description": "Codev CLI - AI-assisted software development framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  }
13
13
 
14
14
  body {
15
- background: #1a1a2e;
15
+ background: #000000;
16
16
  color: #e0e0e0;
17
17
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
18
  overflow: hidden;
@@ -91,12 +91,17 @@
91
91
  font-weight: bold;
92
92
  }
93
93
 
94
- button.view-btn.positive {
95
- color: #4ade80;
94
+ /* Color view buttons to match axes: X=red, Y=green, Z=blue */
95
+ button.view-btn.axis-x {
96
+ color: #ef4444;
97
+ }
98
+
99
+ button.view-btn.axis-y {
100
+ color: #22c55e;
96
101
  }
97
102
 
98
- button.view-btn.negative {
99
- color: #f87171;
103
+ button.view-btn.axis-z {
104
+ color: #3b82f6;
100
105
  }
101
106
 
102
107
  #canvas-container {
@@ -177,15 +182,15 @@
177
182
  <div id="controls">
178
183
  <div class="control-group">
179
184
  <span class="control-label">View:</span>
180
- <button id="viewTop" class="view-btn positive" title="Top (+Y)">+Y</button>
181
- <button id="viewBottom" class="view-btn negative" title="Bottom (-Y)">-Y</button>
182
- <button id="viewFront" class="view-btn positive" title="Front (+Z)">+Z</button>
183
- <button id="viewBack" class="view-btn negative" title="Back (-Z)">-Z</button>
184
- <button id="viewRight" class="view-btn positive" title="Right (+X)">+X</button>
185
- <button id="viewLeft" class="view-btn negative" title="Left (-X)">-X</button>
185
+ <button id="viewRight" class="view-btn axis-x" title="Right (from +X)">Right</button>
186
+ <button id="viewLeft" class="view-btn axis-x" title="Left (from -X)">Left</button>
187
+ <button id="viewFront" class="view-btn axis-y" title="Front (from -Y)">Front</button>
188
+ <button id="viewBack" class="view-btn axis-y" title="Back (from +Y)">Back</button>
189
+ <button id="viewTop" class="view-btn axis-z" title="Top (looking down Z)">Top</button>
190
+ <button id="viewBottom" class="view-btn axis-z" title="Bottom (looking up)">Bot</button>
186
191
  </div>
187
192
  <div class="control-group">
188
- <button id="viewIso" title="Isometric view">Iso</button>
193
+ <button id="viewIso" title="Toggle perspective/orthographic">Persp</button>
189
194
  <button id="resetBtn" title="Fit model to view">Fit</button>
190
195
  </div>
191
196
  <div class="control-group">
@@ -207,9 +212,9 @@
207
212
  <div id="error-message">Failed to load 3D model</div>
208
213
  </div>
209
214
  <div id="axes-legend">
210
- <div class="axis-x">X &#8594; Left</div>
211
- <div class="axis-y">Y &#8594; Front</div>
212
- <div class="axis-z">Z &#8594; Up</div>
215
+ <div class="axis-x">X Right</div>
216
+ <div class="axis-y">Y Front</div>
217
+ <div class="axis-z">Z Up</div>
213
218
  </div>
214
219
  </div>
215
220
 
@@ -235,13 +240,14 @@
235
240
  const FORMAT = '{{FORMAT}}'; // 'stl' or '3mf'
236
241
 
237
242
  // Three.js setup
238
- let scene, camera, renderer, controls;
243
+ let scene, camera, orthoCamera, perspCamera, renderer, controls;
239
244
  let model = null; // Can be mesh (STL) or group (3MF)
240
245
  let wireframeMesh = null; // Wireframe overlay mesh
241
246
  let axesHelper, gridHelper;
242
247
  let viewMode = 'solid'; // 'solid', 'wireframe', or 'both'
243
248
  let showAxes = true;
244
249
  let showGrid = true;
250
+ let isOrthographic = false;
245
251
  let modelCenter = new THREE.Vector3();
246
252
  let cameraDistance = 100;
247
253
 
@@ -270,17 +276,25 @@
270
276
  function init() {
271
277
  // Scene
272
278
  scene = new THREE.Scene();
273
- scene.background = new THREE.Color(0x1a1a2e);
274
-
275
- // Camera (Z-up coordinate system)
276
- camera = new THREE.PerspectiveCamera(
277
- 45,
278
- container.clientWidth / container.clientHeight,
279
- 0.1,
280
- 10000
279
+ scene.background = new THREE.Color(0x000000);
280
+
281
+ // Cameras (Z-up coordinate system)
282
+ const aspect = container.clientWidth / container.clientHeight;
283
+ perspCamera = new THREE.PerspectiveCamera(45, aspect, 0.1, 10000);
284
+ perspCamera.up.set(0, 0, 1); // Z is up
285
+ perspCamera.position.set(100, 100, 100);
286
+
287
+ // Orthographic camera (size will be adjusted when model loads)
288
+ const frustumSize = 100;
289
+ orthoCamera = new THREE.OrthographicCamera(
290
+ -frustumSize * aspect / 2, frustumSize * aspect / 2,
291
+ frustumSize / 2, -frustumSize / 2,
292
+ 0.1, 10000
281
293
  );
282
- camera.up.set(0, 0, 1); // Z is up
283
- camera.position.set(100, 100, 100);
294
+ orthoCamera.up.set(0, 0, 1);
295
+ orthoCamera.position.set(100, 100, 100);
296
+
297
+ camera = perspCamera; // Start with perspective
284
298
 
285
299
  // Renderer
286
300
  renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
@@ -307,12 +321,15 @@
307
321
  directionalLight2.position.set(-1, -1, -1);
308
322
  scene.add(directionalLight2);
309
323
 
310
- // Grid (on XY plane for Z-up coordinate system)
311
- gridHelper = new THREE.GridHelper(200, 20, 0x0f3460, 0x0f3460);
312
- gridHelper.rotation.x = Math.PI / 2; // Rotate to XY plane
324
+ // Grid on XY plane (Z=0) for Z-up coordinate system
325
+ // GridHelper(size, divisions) - 10mm squares, 200mm total = 20 divisions
326
+ gridHelper = new THREE.GridHelper(200, 20, 0x808080, 0x808080);
327
+ gridHelper.rotation.x = Math.PI / 2; // Rotate from XZ to XY plane
328
+ gridHelper.material.transparent = true;
329
+ gridHelper.material.opacity = 0.5;
313
330
  scene.add(gridHelper);
314
331
 
315
- // Axes helper (X=red, Y=green, Z=blue)
332
+ // Axes helper at origin
316
333
  axesHelper = new THREE.AxesHelper(50);
317
334
  scene.add(axesHelper);
318
335
 
@@ -328,14 +345,14 @@
328
345
  axesBtn.addEventListener('click', toggleAxes);
329
346
  gridBtn.addEventListener('click', toggleGrid);
330
347
 
331
- // View buttons (Z-up coordinate system: X=left, Y=front, Z=up)
348
+ // View buttons (Z-up coordinate system: X=right, Y=front, Z=up)
332
349
  viewTop.addEventListener('click', () => setView(0, 0, 1)); // Looking down from +Z
333
350
  viewBottom.addEventListener('click', () => setView(0, 0, -1)); // Looking up from -Z
334
- viewFront.addEventListener('click', () => setView(0, -1, 0)); // Looking from -Y (front)
335
- viewBack.addEventListener('click', () => setView(0, 1, 0)); // Looking from +Y (back)
336
- viewRight.addEventListener('click', () => setView(1, 0, 0)); // Looking from +X
337
- viewLeft.addEventListener('click', () => setView(-1, 0, 0)); // Looking from -X
338
- viewIso.addEventListener('click', () => setView(1, -1, 1)); // Isometric from front-right-above
351
+ viewFront.addEventListener('click', () => setView(0, -1, 0)); // Looking from -Y
352
+ viewBack.addEventListener('click', () => setView(0, 1, 0)); // Looking from +Y
353
+ viewRight.addEventListener('click', () => setView(1, 0, 0)); // Looking from +X (right side)
354
+ viewLeft.addEventListener('click', () => setView(-1, 0, 0)); // Looking from -X (left side)
355
+ viewIso.addEventListener('click', toggleOrthographic); // Toggle perspective/orthographic
339
356
 
340
357
  // Animation loop
341
358
  animate();
@@ -402,15 +419,7 @@
402
419
  (group) => {
403
420
  // 3MFLoader returns a Group with meshes
404
421
  // 3MF uses Z-up natively, which matches our coordinate system
405
-
406
- // Center the group in XY
407
- const box = new THREE.Box3().setFromObject(group);
408
- const center = box.getCenter(new THREE.Vector3());
409
- group.position.sub(center);
410
-
411
- // Move to sit on grid (Z=0 plane)
412
- const newBox = new THREE.Box3().setFromObject(group);
413
- group.position.z -= newBox.min.z;
422
+ // Keep model at original position (don't center) to preserve coordinate alignment
414
423
 
415
424
  model = group;
416
425
  scene.add(model);
@@ -446,14 +455,26 @@
446
455
  const box = new THREE.Box3().setFromObject(object);
447
456
  box.getCenter(modelCenter);
448
457
  const size = box.getSize(new THREE.Vector3());
449
- cameraDistance = Math.max(size.x, size.y, size.z) * 2;
450
-
451
- // Scale grid and axes to model size
452
458
  const maxDim = Math.max(size.x, size.y, size.z);
453
- const gridSize = Math.ceil(maxDim * 2 / 10) * 10; // Round up to nearest 10
459
+ cameraDistance = maxDim * 2;
460
+
461
+ // Configure orthographic camera frustum based on model size
462
+ const aspect = container.clientWidth / container.clientHeight;
463
+ const frustumSize = maxDim * 1.5;
464
+ orthoCamera.left = -frustumSize * aspect / 2;
465
+ orthoCamera.right = frustumSize * aspect / 2;
466
+ orthoCamera.top = frustumSize / 2;
467
+ orthoCamera.bottom = -frustumSize / 2;
468
+ orthoCamera.updateProjectionMatrix();
469
+
470
+ // Scale grid and axes to model size - always 10mm squares
471
+ const gridSize = Math.ceil(maxDim * 2 / 10) * 10; // Round up to nearest 10mm
472
+ const divisions = gridSize / 10; // 10mm per square
454
473
  scene.remove(gridHelper);
455
- gridHelper = new THREE.GridHelper(gridSize, gridSize / 5, 0x0f3460, 0x0f3460);
456
- gridHelper.rotation.x = Math.PI / 2; // Rotate to XY plane for Z-up
474
+ gridHelper = new THREE.GridHelper(gridSize, divisions, 0x808080, 0x808080);
475
+ gridHelper.rotation.x = Math.PI / 2; // Rotate from XZ to XY plane
476
+ gridHelper.material.transparent = true;
477
+ gridHelper.material.opacity = 0.5;
457
478
  gridHelper.visible = showGrid;
458
479
  scene.add(gridHelper);
459
480
 
@@ -462,8 +483,9 @@
462
483
  axesHelper.visible = showAxes;
463
484
  scene.add(axesHelper);
464
485
 
465
- // Fit camera to model
466
- fitToView();
486
+ // Set camera to look at model center, default front view
487
+ controls.target.copy(modelCenter);
488
+ setView(0, -1, 0); // Front view
467
489
 
468
490
  // Hide loading
469
491
  loading.classList.add('hidden');
@@ -481,13 +503,46 @@
481
503
 
482
504
  const box = new THREE.Box3().setFromObject(model);
483
505
  const size = box.getSize(new THREE.Vector3());
506
+ const center = box.getCenter(new THREE.Vector3());
484
507
 
485
508
  const maxDim = Math.max(size.x, size.y, size.z);
486
- const fov = camera.fov * (Math.PI / 180);
509
+ const fov = perspCamera.fov * (Math.PI / 180);
487
510
  cameraDistance = maxDim / (2 * Math.tan(fov / 2)) * 1.5;
488
511
 
489
- // Isometric view from front-right-above (Z-up system)
490
- setView(1, -1, 1);
512
+ // Update orthographic frustum
513
+ const aspect = container.clientWidth / container.clientHeight;
514
+ const frustumSize = maxDim * 1.5;
515
+ orthoCamera.left = -frustumSize * aspect / 2;
516
+ orthoCamera.right = frustumSize * aspect / 2;
517
+ orthoCamera.top = frustumSize / 2;
518
+ orthoCamera.bottom = -frustumSize / 2;
519
+ orthoCamera.updateProjectionMatrix();
520
+
521
+ // Keep current view direction, just adjust distance
522
+ const currentDir = new THREE.Vector3().subVectors(camera.position, controls.target).normalize();
523
+ camera.position.copy(center).add(currentDir.multiplyScalar(cameraDistance));
524
+ controls.target.copy(center);
525
+ controls.update();
526
+ }
527
+
528
+ function toggleOrthographic() {
529
+ isOrthographic = !isOrthographic;
530
+
531
+ // Copy position and orientation from current camera to the other
532
+ const newCamera = isOrthographic ? orthoCamera : perspCamera;
533
+ newCamera.position.copy(camera.position);
534
+ newCamera.up.copy(camera.up);
535
+ newCamera.lookAt(controls.target);
536
+
537
+ camera = newCamera;
538
+
539
+ // Update controls to use new camera
540
+ controls.object = camera;
541
+ controls.update();
542
+
543
+ // Update button state
544
+ viewIso.classList.toggle('active', isOrthographic);
545
+ viewIso.textContent = isOrthographic ? 'Ortho' : 'Persp';
491
546
  }
492
547
 
493
548
  function setView(x, y, z) {
@@ -633,8 +688,18 @@
633
688
  }
634
689
 
635
690
  function onResize() {
636
- camera.aspect = container.clientWidth / container.clientHeight;
637
- camera.updateProjectionMatrix();
691
+ const aspect = container.clientWidth / container.clientHeight;
692
+
693
+ // Update perspective camera
694
+ perspCamera.aspect = aspect;
695
+ perspCamera.updateProjectionMatrix();
696
+
697
+ // Update orthographic camera (maintain frustum height, adjust width)
698
+ const frustumHeight = orthoCamera.top - orthoCamera.bottom;
699
+ orthoCamera.left = -frustumHeight * aspect / 2;
700
+ orthoCamera.right = frustumHeight * aspect / 2;
701
+ orthoCamera.updateProjectionMatrix();
702
+
638
703
  renderer.setSize(container.clientWidth, container.clientHeight);
639
704
  controls.handleResize(); // TrackballControls needs this to update screen dimensions
640
705
  }
@@ -182,11 +182,34 @@
182
182
  color: #888;
183
183
  }
184
184
 
185
- /* Editor textarea */
186
- #editor {
185
+ /* Editor container with line numbers */
186
+ #editor-container {
187
187
  display: none;
188
- width: 100%;
189
188
  height: calc(100vh - 80px);
189
+ position: relative;
190
+ }
191
+ #editor-line-numbers {
192
+ position: absolute;
193
+ left: 0;
194
+ top: 0;
195
+ bottom: 0;
196
+ width: 50px;
197
+ background: #252525;
198
+ color: #666;
199
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
200
+ font-size: 13px;
201
+ line-height: 1.5;
202
+ padding: 15px 8px 15px 0;
203
+ text-align: right;
204
+ overflow: hidden;
205
+ border-right: 1px solid #333;
206
+ user-select: none;
207
+ box-sizing: border-box;
208
+ }
209
+ #editor {
210
+ width: calc(100% - 50px);
211
+ height: 100%;
212
+ margin-left: 50px;
190
213
  font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
191
214
  font-size: 13px;
192
215
  line-height: 1.5;
@@ -473,8 +496,11 @@
473
496
  </div>
474
497
  </div>
475
498
 
476
- <!-- Editor mode -->
477
- <textarea id="editor" spellcheck="false"></textarea>
499
+ <!-- Editor mode with line numbers -->
500
+ <div id="editor-container">
501
+ <div id="editor-line-numbers"></div>
502
+ <textarea id="editor" spellcheck="false"></textarea>
503
+ </div>
478
504
 
479
505
  <!-- Search Bar -->
480
506
  <div class="search-bar" id="searchBar">
@@ -1279,6 +1305,7 @@
1279
1305
 
1280
1306
  async function toggleEditMode() {
1281
1307
  const viewMode = document.getElementById('viewMode');
1308
+ const editorContainer = document.getElementById('editor-container');
1282
1309
  const editor = document.getElementById('editor');
1283
1310
  const editBtn = document.getElementById('editBtn');
1284
1311
  const saveBtn = document.getElementById('saveBtn');
@@ -1302,14 +1329,15 @@
1302
1329
  // to ensure Cancel always reverts to the true disk state
1303
1330
  editor.value = currentContent;
1304
1331
  viewMode.style.display = 'none';
1305
- editor.style.display = 'block';
1332
+ editorContainer.style.display = 'block';
1306
1333
  editBtn.textContent = 'Switch to Annotate';
1307
1334
  subtitle.textContent = 'Edit the file directly by clicking and typing.';
1308
1335
  saveBtn.style.display = 'inline-block';
1309
1336
  cancelBtn.style.display = 'inline-block';
1310
1337
  editMode = true;
1311
1338
 
1312
- // Apply scroll position to editor
1339
+ // Update line numbers and apply scroll position
1340
+ updateEditorLineNumbers();
1313
1341
  editor.scrollTop = scrollTop;
1314
1342
  editor.focus();
1315
1343
  } else {
@@ -1324,7 +1352,7 @@
1324
1352
 
1325
1353
  currentContent = editor.value;
1326
1354
  fileLines = currentContent.split('\n');
1327
- editor.style.display = 'none';
1355
+ editorContainer.style.display = 'none';
1328
1356
  viewMode.style.display = 'grid';
1329
1357
  editBtn.textContent = 'Switch to Editing';
1330
1358
  subtitle.textContent = 'Click on a line number to leave an annotation.';
@@ -1343,6 +1371,19 @@
1343
1371
  }
1344
1372
  }
1345
1373
 
1374
+ // Update line numbers in editor
1375
+ function updateEditorLineNumbers() {
1376
+ const editor = document.getElementById('editor');
1377
+ const lineNumbers = document.getElementById('editor-line-numbers');
1378
+ const lines = editor.value.split('\n');
1379
+ lineNumbers.innerHTML = lines.map((_, i) => `<div>${i + 1}</div>`).join('');
1380
+ }
1381
+
1382
+ // Sync line numbers scroll with editor scroll
1383
+ document.getElementById('editor').addEventListener('scroll', function() {
1384
+ document.getElementById('editor-line-numbers').scrollTop = this.scrollTop;
1385
+ });
1386
+
1346
1387
  async function saveEdit() {
1347
1388
  const editor = document.getElementById('editor');
1348
1389
  const content = editor.value;
@@ -1398,10 +1439,11 @@
1398
1439
  setTimeout(() => notification.remove(), 3000);
1399
1440
  }
1400
1441
 
1401
- // Track unsaved changes in editor
1442
+ // Track unsaved changes in editor and update line numbers
1402
1443
  document.getElementById('editor').addEventListener('input', () => {
1403
1444
  hasUnsavedChanges = document.getElementById('editor').value !== originalContent;
1404
1445
  updateUnsavedIndicator();
1446
+ updateEditorLineNumbers();
1405
1447
  });
1406
1448
 
1407
1449
  // Tab key handling for indentation