cyclecad 3.2.1 → 3.4.0

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.
Files changed (65) hide show
  1. package/DOCKER-SETUP-VERIFICATION.md +399 -0
  2. package/DOCKER-TESTING.md +463 -0
  3. package/FUSION360_MODULES.md +478 -0
  4. package/FUSION_MODULES_README.md +352 -0
  5. package/INTEGRATION_SNIPPETS.md +608 -0
  6. package/KILLER-FEATURES-DELIVERY.md +469 -0
  7. package/MODULES_SUMMARY.txt +337 -0
  8. package/QUICK_REFERENCE.txt +298 -0
  9. package/README-DOCKER-TESTING.txt +438 -0
  10. package/app/index.html +23 -10
  11. package/app/js/fusion-help.json +1808 -0
  12. package/app/js/help-module-v3.js +1096 -0
  13. package/app/js/killer-features-help.json +395 -0
  14. package/app/js/killer-features.js +1508 -0
  15. package/app/js/modules/fusion-assembly.js +842 -0
  16. package/app/js/modules/fusion-cam.js +785 -0
  17. package/app/js/modules/fusion-data.js +814 -0
  18. package/app/js/modules/fusion-drawing.js +844 -0
  19. package/app/js/modules/fusion-inspection.js +756 -0
  20. package/app/js/modules/fusion-render.js +774 -0
  21. package/app/js/modules/fusion-simulation.js +986 -0
  22. package/app/js/modules/fusion-sketch.js +1044 -0
  23. package/app/js/modules/fusion-solid.js +1095 -0
  24. package/app/js/modules/fusion-surface.js +949 -0
  25. package/app/tests/FUSION_TEST_SUITE.md +266 -0
  26. package/app/tests/README.md +77 -0
  27. package/app/tests/TESTING-CHECKLIST.md +177 -0
  28. package/app/tests/TEST_SUITE_SUMMARY.txt +236 -0
  29. package/app/tests/brep-live-test.html +848 -0
  30. package/app/tests/docker-integration-test.html +811 -0
  31. package/app/tests/fusion-all-tests.html +670 -0
  32. package/app/tests/fusion-assembly-tests.html +461 -0
  33. package/app/tests/fusion-cam-tests.html +421 -0
  34. package/app/tests/fusion-simulation-tests.html +421 -0
  35. package/app/tests/fusion-sketch-tests.html +613 -0
  36. package/app/tests/fusion-solid-tests.html +529 -0
  37. package/app/tests/index.html +453 -0
  38. package/app/tests/killer-features-test.html +509 -0
  39. package/app/tests/run-tests.html +874 -0
  40. package/app/tests/step-import-live-test.html +1115 -0
  41. package/app/tests/test-agent-v3.html +93 -696
  42. package/architecture-dashboard.html +1970 -0
  43. package/docs/API-REFERENCE.md +1423 -0
  44. package/docs/BREP-LIVE-TEST-GUIDE.md +453 -0
  45. package/docs/DEVELOPER-GUIDE-v3.md +795 -0
  46. package/docs/DOCKER-QUICK-TEST.md +376 -0
  47. package/docs/FUSION-FEATURES-GUIDE.md +2513 -0
  48. package/docs/FUSION-TUTORIAL.md +1203 -0
  49. package/docs/INFRASTRUCTURE-GUIDE-INDEX.md +327 -0
  50. package/docs/KEYBOARD-SHORTCUTS.md +402 -0
  51. package/docs/KILLER-FEATURES-INTEGRATION.md +412 -0
  52. package/docs/KILLER-FEATURES-SUMMARY.md +424 -0
  53. package/docs/KILLER-FEATURES-TUTORIAL.md +784 -0
  54. package/docs/KILLER-FEATURES.md +562 -0
  55. package/docs/QUICK-REFERENCE.md +282 -0
  56. package/docs/README-v3-DOCS.md +274 -0
  57. package/docs/TUTORIAL-v3.md +1190 -0
  58. package/docs/architecture-dashboard.html +1970 -0
  59. package/docs/architecture-v3.html +1038 -0
  60. package/linkedin-post-v3.md +58 -0
  61. package/package.json +1 -1
  62. package/scripts/dev-setup.sh +338 -0
  63. package/scripts/docker-health-check.sh +159 -0
  64. package/scripts/integration-test.sh +311 -0
  65. package/scripts/test-docker.sh +515 -0
@@ -0,0 +1,795 @@
1
+ # cycleCAD v3.2 Developer Guide
2
+
3
+ Complete reference for developers extending cycleCAD through modules, plugins, and API integration.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Kernel Architecture](#kernel-architecture)
8
+ 2. [Module Development](#module-development)
9
+ 3. [Event System](#event-system)
10
+ 4. [Command API](#command-api)
11
+ 5. [Plugin Development](#plugin-development)
12
+ 6. [Testing](#testing)
13
+ 7. [Contributing](#contributing)
14
+
15
+ ---
16
+
17
+ ## Kernel Architecture
18
+
19
+ ### LEGO Microkernel Concept
20
+
21
+ cycleCAD uses a **LEGO microkernel** pattern: minimal core with maximum modularity.
22
+
23
+ ```javascript
24
+ // Kernel (100 lines)
25
+ const kernel = {
26
+ modules: Map,
27
+ state: Object,
28
+ eventBus: EventEmitter,
29
+
30
+ register(moduleDef) { ... },
31
+ activate(name) { ... },
32
+ exec(command, params) { ... },
33
+ on(event, handler) { ... }
34
+ }
35
+
36
+ // Each module is a LEGO brick
37
+ // Kernel provides the base plate
38
+ // Developers snap in bricks (modules)
39
+ ```
40
+
41
+ ### Why Microkernel?
42
+
43
+ - **Loose coupling**: Modules don't directly depend on each other
44
+ - **Easy testing**: Modules can be tested in isolation
45
+ - **Extensibility**: Add new modules without modifying core
46
+ - **Hot-reload**: Swap modules at runtime (advanced)
47
+ - **Scalability**: 21 modules can grow to 50+ without bloat
48
+
49
+ ### Kernel API
50
+
51
+ ```javascript
52
+ // Register a module
53
+ kernel.register({
54
+ name: 'myModule',
55
+ init() { /* setup */ },
56
+ deactivate() { /* cleanup */ },
57
+ commands: { myCommand() { ... } }
58
+ })
59
+
60
+ // Activate workspace (activates relevant modules)
61
+ kernel.activate('Design') // activates sketch, modeling, features
62
+
63
+ // Execute command
64
+ await kernel.exec('shape.cylinder', { d: 50, h: 80 })
65
+
66
+ // Listen to events
67
+ kernel.on('model:changed', (delta) => { ... })
68
+
69
+ // Get/set state
70
+ kernel.state.get('activeModel')
71
+ kernel.state.set('activeModel', model)
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Module Development
77
+
78
+ ### Module Lifecycle
79
+
80
+ ```
81
+ 1. register() → Module registered with kernel
82
+ 2. init() → Module initializes (load data, setup UI)
83
+ 3. activate() → Module becomes active in workspace
84
+ 4. commands... → User invokes commands
85
+ 5. deactivate() → Module stops (user switched workspace)
86
+ 6. unload() → Module removed from memory (optional)
87
+ ```
88
+
89
+ ### Module Template
90
+
91
+ ```javascript
92
+ // app/js/modules/myfeature.js
93
+ export const MyFeatureModule = {
94
+ // Metadata
95
+ name: 'myfeature',
96
+ version: '1.0.0',
97
+ dependencies: ['viewport', 'tree'], // Modules this depends on
98
+
99
+ // Lifecycle
100
+ init() {
101
+ // Called when module loads
102
+ // Set up UI, event listeners, state
103
+ console.log('[MyFeature] Initializing...')
104
+ },
105
+
106
+ deactivate() {
107
+ // Called when workspace changes
108
+ // Clean up listeners, hide UI
109
+ console.log('[MyFeature] Deactivating...')
110
+ },
111
+
112
+ // Commands (callable via API)
113
+ commands: {
114
+ myCommand(params) {
115
+ // { name, value, ... }
116
+ // Return result or throw error
117
+ return { success: true, result: {...} }
118
+ },
119
+
120
+ anotherCommand(params) {
121
+ // Can be async
122
+ return fetch('/api/data').then(r => r.json())
123
+ }
124
+ },
125
+
126
+ // Events this module publishes
127
+ events: [
128
+ 'myfeature:action-completed',
129
+ 'myfeature:error'
130
+ ],
131
+
132
+ // UI Panel (optional)
133
+ getUI() {
134
+ return `
135
+ <div id="myfeature-panel">
136
+ <button id="my-btn">Click me</button>
137
+ </div>
138
+ `
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Complete Example: Simple Calculator Module
144
+
145
+ ```javascript
146
+ export const CalculatorModule = {
147
+ name: 'calculator',
148
+ version: '1.0.0',
149
+ dependencies: [],
150
+
151
+ init() {
152
+ this.result = 0
153
+ this.kernel = window.kernel
154
+ },
155
+
156
+ commands: {
157
+ add(params) {
158
+ const { a, b } = params
159
+ this.result = a + b
160
+ this.kernel.emit('calculator:result', { result: this.result })
161
+ return { result: this.result }
162
+ },
163
+
164
+ multiply(params) {
165
+ const { a, b } = params
166
+ this.result = a * b
167
+ this.kernel.emit('calculator:result', { result: this.result })
168
+ return { result: this.result }
169
+ },
170
+
171
+ getResult(params) {
172
+ return { result: this.result }
173
+ }
174
+ },
175
+
176
+ events: ['calculator:result'],
177
+
178
+ getUI() {
179
+ return `
180
+ <div id="calculator-panel">
181
+ <input id="calc-input" type="text" value="0" readonly>
182
+ <button onclick="kernel.exec('calculator.add', {a: 5, b: 3})">5+3</button>
183
+ <button onclick="kernel.exec('calculator.multiply', {a: 5, b: 3})">5×3</button>
184
+ </div>
185
+ `
186
+ }
187
+ }
188
+ ```
189
+
190
+ ### Module Best Practices
191
+
192
+ 1. **Single Responsibility**: One module = one feature (sketch, modeling, etc.)
193
+ 2. **Declare Dependencies**: List modules you depend on
194
+ 3. **Emit Events**: Let other modules react to your actions
195
+ 4. **No Direct Imports**: Don't import other modules directly; use kernel.exec()
196
+ 5. **Clean UI**: getUI() should return clean HTML, no inline styles
197
+ 6. **Error Handling**: Throw descriptive errors
198
+ 7. **Type Checking**: Validate params in commands
199
+
200
+ ---
201
+
202
+ ## Event System
203
+
204
+ ### Event Categories
205
+
206
+ cycleCAD uses 30+ core events. Modules can publish custom events.
207
+
208
+ #### Model Events
209
+ ```javascript
210
+ kernel.on('model:created', (payload) => { ... })
211
+ kernel.on('model:modified', (payload) => { ... })
212
+ kernel.on('model:deleted', (payload) => { ... })
213
+
214
+ // Payload example
215
+ {
216
+ modelId: 'abc123',
217
+ timestamp: 1234567890,
218
+ delta: { property: 'oldValue' → 'newValue' }
219
+ }
220
+ ```
221
+
222
+ #### Geometry Events
223
+ ```javascript
224
+ kernel.on('geometry:changed', (payload) => { ... })
225
+ kernel.on('mesh:updated', (payload) => { ... })
226
+
227
+ // Payload
228
+ {
229
+ meshId: 'mesh-0',
230
+ vertices: [...],
231
+ faces: [...],
232
+ bbox: { min, max }
233
+ }
234
+ ```
235
+
236
+ #### UI Events
237
+ ```javascript
238
+ kernel.on('ui:panel-opened', (payload) => { ... })
239
+ kernel.on('ui:selection-changed', (payload) => { ... })
240
+
241
+ // Payload
242
+ {
243
+ panelName: 'properties',
244
+ selectedIds: ['face-0', 'face-1']
245
+ }
246
+ ```
247
+
248
+ #### Workspace Events
249
+ ```javascript
250
+ kernel.on('workspace:switched', (payload) => { ... })
251
+
252
+ // Payload
253
+ {
254
+ from: 'Design',
255
+ to: 'CAM',
256
+ activeModules: ['cam-pipeline', 'viewport', 'toolbar']
257
+ }
258
+ ```
259
+
260
+ ### Publishing Custom Events
261
+
262
+ ```javascript
263
+ // From within a module
264
+ this.kernel.emit('mymodule:action-done', {
265
+ actionId: 'cut-001',
266
+ duration: 1200, // milliseconds
267
+ result: {...}
268
+ })
269
+
270
+ // Listen from another module
271
+ kernel.on('mymodule:action-done', (payload) => {
272
+ console.log('Action completed:', payload.actionId)
273
+ })
274
+ ```
275
+
276
+ ### Event Best Practices
277
+
278
+ 1. **Namespace events**: `modulename:action` (e.g., `cam:toolpath-generated`)
279
+ 2. **Include metadata**: Always send timestamp, IDs, and relevant data
280
+ 3. **Fire after state change**: Event = notification of completed action
281
+ 4. **Don't fire on every frame**: Only on meaningful changes
282
+ 5. **Handle async carefully**: Emit after async completes (promises)
283
+
284
+ ---
285
+
286
+ ## Command API
287
+
288
+ ### 55 Core Commands
289
+
290
+ Commands are functions exposed through the kernel. Any module can call any command.
291
+
292
+ #### Shape Commands (10)
293
+ ```javascript
294
+ kernel.exec('shape.cylinder', { d: 50, h: 80 })
295
+ kernel.exec('shape.box', { w: 100, d: 60, h: 40 })
296
+ kernel.exec('shape.sphere', { r: 50 })
297
+ kernel.exec('shape.cone', { d: 50, h: 100 })
298
+ kernel.exec('shape.torus', { majorR: 50, minorR: 10 })
299
+ kernel.exec('shape.wedge', { w: 100, h: 50, d: 30 })
300
+ kernel.exec('shape.extrude', { sketchId: 'sk-0', depth: 50 })
301
+ kernel.exec('shape.revolve', { sketchId: 'sk-0', axis: [0,0,1], angle: 360 })
302
+ kernel.exec('shape.sweep', { profileId: 'prof', pathId: 'path' })
303
+ kernel.exec('shape.loft', { profileIds: ['p0', 'p1', 'p2'] })
304
+ ```
305
+
306
+ #### Feature Commands (10)
307
+ ```javascript
308
+ kernel.exec('feature.fillet', { edges: [0, 1, 2], radius: 5 })
309
+ kernel.exec('feature.chamfer', { edges: [0, 1], distance: 2 })
310
+ kernel.exec('feature.pattern', { type: 'rectangular', x: 3, y: 2, spacing: 50 })
311
+ kernel.exec('feature.mirror', { body: 'body-0', plane: 'XZ' })
312
+ kernel.exec('feature.shell', { thickness: 2 })
313
+ kernel.exec('feature.draft', { angle: 5 })
314
+ kernel.exec('feature.thread', { pitch: 1.75, type: 'ISO' })
315
+ kernel.exec('feature.split', { tool: 'plane-0' })
316
+ kernel.exec('feature.wrap', { surface: 'surf-0' })
317
+ kernel.exec('feature.scale', { factor: 1.5 })
318
+ ```
319
+
320
+ #### Assembly Commands (8)
321
+ ```javascript
322
+ kernel.exec('assembly.addComponent', { file: 'part.step', position: [0,0,0] })
323
+ kernel.exec('assembly.createJoint', { type: 'mate', body1: 'b1', body2: 'b2', face1: 'f1', face2: 'f2' })
324
+ kernel.exec('assembly.explode', { mode: 'radial' })
325
+ kernel.exec('assembly.generateBOM', { })
326
+ kernel.exec('assembly.checkInterference', { })
327
+ kernel.exec('assembly.drive', { jointId: 'j0', value: 0.5 })
328
+ kernel.exec('assembly.pattern', { type: 'rectangular', count: 4 })
329
+ kernel.exec('assembly.hideComponent', { componentId: 'c-0' })
330
+ ```
331
+
332
+ #### Export Commands (8)
333
+ ```javascript
334
+ kernel.exec('export.stl', { format: 'binary' })
335
+ kernel.exec('export.step', { })
336
+ kernel.exec('export.gltf', { includeAnimation: true })
337
+ kernel.exec('export.obj', { })
338
+ kernel.exec('export.pdf', { paperSize: 'A4', scale: 1 })
339
+ kernel.exec('export.dxf', { })
340
+ kernel.exec('export.json', { })
341
+ kernel.exec('export.png', { width: 1920, height: 1080, dpi: 300 })
342
+ ```
343
+
344
+ #### Import Commands (6)
345
+ ```javascript
346
+ kernel.exec('import.step', { file: blob })
347
+ kernel.exec('import.iges', { file: blob })
348
+ kernel.exec('import.obj', { file: blob })
349
+ kernel.exec('import.stl', { file: blob })
350
+ kernel.exec('import.stp', { file: blob })
351
+ kernel.exec('import.ipt', { file: blob })
352
+ ```
353
+
354
+ #### Validation Commands (6)
355
+ ```javascript
356
+ kernel.exec('validate.designReview', { })
357
+ kernel.exec('validate.checkInterference', { })
358
+ kernel.exec('validate.estimateCost', { material: 'aluminum' })
359
+ kernel.exec('validate.estimateWeight', { })
360
+ kernel.exec('validate.manufacturability', { process: 'cnc' })
361
+ kernel.exec('validate.fea', { type: 'static' })
362
+ ```
363
+
364
+ #### AI Commands (5)
365
+ ```javascript
366
+ kernel.exec('ai.textToCAD', { prompt: 'socket head bolt M10' })
367
+ kernel.exec('ai.identifyPart', { image: blob })
368
+ kernel.exec('ai.suggestFeature', { context: 'current model' })
369
+ kernel.exec('ai.generateDesign', { description: '...' })
370
+ kernel.exec('ai.designReview', { })
371
+ ```
372
+
373
+ #### Render Commands (4)
374
+ ```javascript
375
+ kernel.exec('render.snapshot', { width: 1920, height: 1080 })
376
+ kernel.exec('render.multiview', { views: ['front', 'top', 'iso'] })
377
+ kernel.exec('render.fitToObject', { })
378
+ kernel.exec('render.setMaterial', { material: 'steel' })
379
+ ```
380
+
381
+ #### Workspace Commands (3)
382
+ ```javascript
383
+ kernel.exec('workspace.switch', { name: 'CAM' })
384
+ kernel.exec('workspace.getActive', { })
385
+ kernel.exec('workspace.listWorkspaces', { })
386
+ ```
387
+
388
+ #### File Commands (3)
389
+ ```javascript
390
+ kernel.exec('file.save', { path: '/models/bracket.ccad' })
391
+ kernel.exec('file.load', { path: '/models/bracket.ccad' })
392
+ kernel.exec('file.new', { })
393
+ ```
394
+
395
+ ### Command Error Handling
396
+
397
+ ```javascript
398
+ // Commands throw on error
399
+ try {
400
+ const result = await kernel.exec('shape.cylinder', { d: -50 }) // Invalid!
401
+ } catch (error) {
402
+ console.error('Error:', error.message)
403
+ // "Invalid diameter: must be positive"
404
+ }
405
+
406
+ // Check result
407
+ const result = await kernel.exec('shape.cylinder', { d: 50 })
408
+ if (result.success) {
409
+ console.log('Geometry ID:', result.geometryId)
410
+ } else {
411
+ console.error('Failed:', result.error)
412
+ }
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Plugin Development
418
+
419
+ ### What is a Plugin?
420
+
421
+ Plugin = JavaScript module that extends cycleCAD with custom features.
422
+
423
+ **Examples:**
424
+ - Custom 3D printer integration (send to Prusa, Ultimaker)
425
+ - CAD library (fasteners, bearings, motors)
426
+ - Manufacturing specific tools (waterjet cutting, injection molding)
427
+ - Domain-specific features (architectural design, furniture)
428
+
429
+ ### Plugin Structure
430
+
431
+ ```
432
+ my-plugin/
433
+ ├── manifest.json # Plugin metadata
434
+ ├── index.js # Entry point
435
+ ├── modules/
436
+ │ ├── feature1.js # Custom module
437
+ │ └── feature2.js
438
+ ├── assets/
439
+ │ ├── icons/
440
+ │ └── models/
441
+ └── README.md
442
+ ```
443
+
444
+ ### Manifest Format
445
+
446
+ ```json
447
+ {
448
+ "id": "com.example.myplugin",
449
+ "name": "My Plugin",
450
+ "version": "1.0.0",
451
+ "description": "Custom features for cycleCAD",
452
+ "author": "Your Name",
453
+ "license": "MIT",
454
+ "cyclecadVersion": ">=3.2.0",
455
+ "modules": [
456
+ "modules/feature1.js",
457
+ "modules/feature2.js"
458
+ ],
459
+ "permissions": [
460
+ "geometry.read",
461
+ "geometry.write",
462
+ "ui.panel",
463
+ "storage.local",
464
+ "network.request"
465
+ ]
466
+ }
467
+ ```
468
+
469
+ ### Plugin Entry Point
470
+
471
+ ```javascript
472
+ // index.js
473
+ export default {
474
+ async init(kernel) {
475
+ // kernel = the cycleCAD kernel
476
+
477
+ // Register modules
478
+ const Feature1 = (await import('./modules/feature1.js')).default
479
+ kernel.register(Feature1)
480
+
481
+ // Listen to events
482
+ kernel.on('model:created', (payload) => {
483
+ console.log('Model created:', payload.modelId)
484
+ })
485
+
486
+ // Add toolbar button
487
+ const toolbar = document.querySelector('#toolbar')
488
+ const btn = document.createElement('button')
489
+ btn.textContent = 'My Plugin'
490
+ btn.onclick = () => kernel.exec('myplugin.action', {})
491
+ toolbar.appendChild(btn)
492
+
493
+ return {
494
+ name: 'My Plugin',
495
+ version: '1.0.0',
496
+ uninstall() {
497
+ // Cleanup
498
+ }
499
+ }
500
+ }
501
+ }
502
+ ```
503
+
504
+ ### Installing Plugins
505
+
506
+ ```javascript
507
+ // From user action or programmatic
508
+ const pluginUrl = '/plugins/my-plugin/index.js'
509
+ const module = await import(pluginUrl)
510
+ const plugin = await module.default.init(kernel)
511
+ ```
512
+
513
+ ### Plugin Best Practices
514
+
515
+ 1. **Small scope**: One feature per plugin
516
+ 2. **Use kernel events**: Don't hook directly into DOM
517
+ 3. **Error handling**: All commands should handle errors gracefully
518
+ 4. **Documentation**: Include README with examples
519
+ 5. **Performance**: Async operations where needed
520
+ 6. **Permissions**: Request only what you need
521
+
522
+ ---
523
+
524
+ ## Testing
525
+
526
+ ### Unit Testing Modules
527
+
528
+ ```javascript
529
+ // test/myfeature.test.js
530
+ import { MyFeatureModule } from '../app/js/modules/myfeature.js'
531
+
532
+ describe('MyFeature Module', () => {
533
+ let kernel, module
534
+
535
+ beforeEach(() => {
536
+ kernel = createMockKernel()
537
+ module = MyFeatureModule
538
+ module.kernel = kernel
539
+ module.init()
540
+ })
541
+
542
+ test('myCommand returns correct result', () => {
543
+ const result = module.commands.myCommand({ value: 42 })
544
+ expect(result).toEqual({ success: true, value: 42 })
545
+ })
546
+
547
+ test('emits event on action', () => {
548
+ const handler = jest.fn()
549
+ kernel.on('myfeature:action-done', handler)
550
+
551
+ module.commands.doAction({ })
552
+
553
+ expect(handler).toHaveBeenCalled()
554
+ })
555
+ })
556
+ ```
557
+
558
+ ### Integration Testing
559
+
560
+ ```javascript
561
+ // test/integration.test.js
562
+ describe('Sketch → Extrude → Fillet Workflow', () => {
563
+ let kernel
564
+
565
+ beforeEach(async () => {
566
+ kernel = await initializeCycleCAD()
567
+ kernel.activate('Design')
568
+ })
569
+
570
+ test('Can create a filleted box', async () => {
571
+ // Create sketch
572
+ await kernel.exec('sketch.create', { plane: 'XY' })
573
+ await kernel.exec('sketch.rectangle', { w: 100, h: 50 })
574
+
575
+ // Extrude
576
+ const extrudeResult = await kernel.exec('shape.extrude', {
577
+ sketchId: 'sketch-0',
578
+ depth: 50
579
+ })
580
+
581
+ // Fillet
582
+ const filletResult = await kernel.exec('feature.fillet', {
583
+ edges: [0, 1, 2, 3],
584
+ radius: 5
585
+ })
586
+
587
+ // Verify
588
+ expect(filletResult.success).toBe(true)
589
+ expect(filletResult.geometryId).toBeDefined()
590
+ })
591
+ })
592
+ ```
593
+
594
+ ### E2E Testing with Playwright
595
+
596
+ ```javascript
597
+ // test/e2e.test.js
598
+ import { test, expect } from '@playwright/test'
599
+
600
+ test('Create and export STL', async ({ page }) => {
601
+ await page.goto('http://localhost:8080')
602
+
603
+ // Click Design workspace
604
+ await page.click('[data-workspace="Design"]')
605
+
606
+ // Create sketch
607
+ await page.click('[id="sketch-btn"]')
608
+ await page.click('[data-plane="XY"]')
609
+
610
+ // Draw rectangle
611
+ await page.click('[id="rect-tool"]')
612
+ await page.mouse.click(100, 100)
613
+ await page.mouse.click(200, 150)
614
+
615
+ // Extrude
616
+ await page.click('[id="extrude-btn"]')
617
+ await page.fill('[id="extrude-depth"]', '50')
618
+ await page.click('[id="ok-btn"]')
619
+
620
+ // Export
621
+ await page.click('[id="export-btn"]')
622
+ await page.selectOption('[id="export-format"]', 'stl')
623
+
624
+ // Verify download
625
+ const downloadPromise = page.waitForEvent('download')
626
+ await page.click('[id="export-confirm"]')
627
+ const download = await downloadPromise
628
+
629
+ expect(download.suggestedFilename()).toBe('model.stl')
630
+ })
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Contributing
636
+
637
+ ### Code Style
638
+
639
+ - **Language**: ES6+ JavaScript (no TypeScript)
640
+ - **Formatting**: 2 spaces, no semicolons (optional)
641
+ - **Comments**: JSDoc for public APIs
642
+ - **Naming**: camelCase for functions/vars, PascalCase for classes
643
+ - **Linting**: ESLint (see `.eslintrc.json`)
644
+
645
+ ### Git Workflow
646
+
647
+ 1. **Fork** the repository
648
+ 2. **Create branch**: `feature/my-feature` or `fix/my-bug`
649
+ 3. **Write tests**: Every feature needs tests
650
+ 4. **Commit**: Descriptive messages ("Add fillet radius editor")
651
+ 5. **Push**: To your fork
652
+ 6. **PR**: Describe what you're adding/fixing
653
+
654
+ ### PR Checklist
655
+
656
+ - [ ] Tests added/updated
657
+ - [ ] Documentation updated
658
+ - [ ] No linting errors (`npm run lint`)
659
+ - [ ] All tests pass (`npm test`)
660
+ - [ ] Follows code style guide
661
+ - [ ] Feature complete (not WIP)
662
+ - [ ] No breaking changes documented
663
+
664
+ ### Commit Message Format
665
+
666
+ ```
667
+ type(scope): description
668
+
669
+ body (optional)
670
+ footer (optional)
671
+
672
+ // Examples:
673
+ feat(sketch): add tangent constraint (O key)
674
+ fix(viewport): correct mouse offset by 32px
675
+ refactor(kernel): simplify event bus initialization
676
+ docs(tutorial): add sweep and loft examples
677
+ test(assembly): add interference detection tests
678
+ ```
679
+
680
+ Types: `feat`, `fix`, `refactor`, `docs`, `test`, `perf`, `build`
681
+
682
+ ### Issue Reporting
683
+
684
+ Include:
685
+ - Minimal reproduction steps
686
+ - Expected vs actual behavior
687
+ - Browser/OS version
688
+ - Browser console errors
689
+ - Screenshots (for UI issues)
690
+
691
+ ### Feature Requests
692
+
693
+ Include:
694
+ - Use case description
695
+ - Mockup or example (if applicable)
696
+ - Priority (nice-to-have vs critical)
697
+ - Suggested implementation (if applicable)
698
+
699
+ ---
700
+
701
+ ## Advanced Topics
702
+
703
+ ### Hot Module Reloading
704
+
705
+ ```javascript
706
+ // In development, reload module without restarting
707
+ if (module.hot) {
708
+ module.hot.accept('./mymodule.js', () => {
709
+ kernel.unload('myfeature')
710
+ const updated = require('./mymodule.js')
711
+ kernel.register(updated)
712
+ kernel.activate('Design')
713
+ })
714
+ }
715
+ ```
716
+
717
+ ### State Management (Advanced)
718
+
719
+ ```javascript
720
+ // Get full state
721
+ const state = kernel.state.getAll()
722
+
723
+ // Observe state changes
724
+ kernel.state.watch('activeModel', (oldVal, newVal) => {
725
+ console.log('Model changed:', oldVal, '→', newVal)
726
+ })
727
+
728
+ // Batch updates
729
+ kernel.state.beginBatch()
730
+ kernel.state.set('a', 1)
731
+ kernel.state.set('b', 2)
732
+ kernel.state.endBatch() // Single re-render
733
+ ```
734
+
735
+ ### Performance Optimization
736
+
737
+ ```javascript
738
+ // Defer heavy computation
739
+ const result = await kernel.exec('feature.fillet', {
740
+ edges: largeArray,
741
+ async: true, // Non-blocking
742
+ onProgress: (progress) => {
743
+ console.log('Fillet progress:', progress)
744
+ }
745
+ })
746
+
747
+ // Use Web Workers for CAM/FEA
748
+ const worker = new Worker('/workers/cam-worker.js')
749
+ worker.postMessage({ toolpath: paths })
750
+ worker.onmessage = (e) => {
751
+ const gcode = e.data
752
+ }
753
+ ```
754
+
755
+ ---
756
+
757
+ ## Troubleshooting
758
+
759
+ ### Module not found
760
+ - Check manifest.json includes the module
761
+ - Verify file path is correct
762
+ - Check browser console for import errors
763
+
764
+ ### Command fails with "not found"
765
+ - Module may not be activated for current workspace
766
+ - Check kernel.state.get('activeModules')
767
+ - Try switching workspaces
768
+
769
+ ### Events not firing
770
+ - Make sure event name matches (case-sensitive)
771
+ - Check module is initialized (kernel.init())
772
+ - Verify listener added before event fires
773
+
774
+ ### Memory leaks
775
+ - Always unsubscribe from events in deactivate()
776
+ - Clean up DOM elements in deactivate()
777
+ - Close connections (WebSocket, fetch) properly
778
+
779
+ ---
780
+
781
+ ## Useful Resources
782
+
783
+ - **Full API Docs**: `/docs/API-REFERENCE.md`
784
+ - **Architecture**: `/docs/architecture-v3.html`
785
+ - **Examples**: `/examples/` directory
786
+ - **Discord**: Community help
787
+ - **GitHub Issues**: Bug reports, feature requests
788
+
789
+ ---
790
+
791
+ ## License
792
+
793
+ cycleCAD is MIT licensed. Plugins can use any OSS license.
794
+
795
+ **Happy coding!**