Skip to content

Commit 88116fe

Browse files
committed
.
1 parent f17ac5a commit 88116fe

4 files changed

Lines changed: 300 additions & 20 deletions

File tree

elle-app/CLAUDE.md

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,285 @@ The application operates in two modes: Manual and Canned Cycle which can be swit
8484
- Manual mode allows electronic leadscrew with configurable pitch ratios
8585
- Emergency stop preserves work coordinate system
8686
- All measurements displayed in current unit system (mm/inch toggle)
87+
88+
## CRITICAL: Settings Save System - DO NOT BREAK THIS AGAIN!
89+
90+
The settings save system has been broken multiple times. Follow these rules STRICTLY:
91+
92+
### Settings Architecture
93+
1. **Single Source of Truth**: All settings are managed in `elle-frontend/src/composables/useSettings.ts`
94+
2. **Single Save Function**: ONLY use the `saveSettings()` function from useSettings.ts
95+
3. **NO Duplicate Save Logic**: Never create alternative save mechanisms in App.vue or elsewhere
96+
97+
### What NOT to do (these have broken settings multiple times):
98+
1. **NEVER** add `isQuitting` checks that prevent saving during app exit
99+
2. **NEVER** create manual save logic in `quitApplication()` or other functions
100+
3. **NEVER** use `JSON.parse(JSON.stringify())` on Vue reactive objects - it breaks serialization
101+
4. **NEVER** pass Vue refs directly to the save function - always use `.value`
102+
5. **NEVER** block ALL saves when validation fails - this prevents any settings from being saved
103+
104+
### Correct Save Implementation:
105+
```typescript
106+
// ✅ CORRECT: Manual serialization of reactive refs
107+
const settingsToSave = {
108+
tools: tools.value.map(tool => ({
109+
id: tool.id,
110+
offsetX: tool.offsetX,
111+
offsetZ: tool.offsetZ,
112+
description: tool.description
113+
})),
114+
currentToolOffsetX: currentToolOffsetX.value,
115+
currentToolOffsetZ: currentToolOffsetZ.value,
116+
// ... other settings
117+
}
118+
```
119+
120+
### Settings Flow:
121+
1. **Auto-save**: Watcher triggers `saveSettings()` on any change
122+
2. **Manual save**: `quitApplication()` calls `saveSettings()` before exit
123+
3. **Electron IPC**: `window.settings.save()``main.ts``electron-store`
124+
4. **Persistence**: Stored in `~/.config/elle-app/appConfig.json`
125+
126+
### Adding New Settings:
127+
When adding new settings, update EXACTLY these files:
128+
1. `elle-frontend/src/composables/useSettings.ts` (ref definition, save object, return statement, watcher array)
129+
2. `elle-electron/main.ts` (getSettings return, saveSettings handler)
130+
3. `elle-electron/electron-store/configuration.ts` (interface definition)
131+
4. `elle-electron/preload.d.ts` (type definitions)
132+
133+
### Testing Settings:
134+
After any settings change:
135+
1. Run the app and change tool offsets
136+
2. Quit the app normally
137+
3. Check `~/.config/elle-app/appConfig.json` contains all expected data
138+
4. Restart app and verify settings are loaded correctly
139+
140+
### Emergency Recovery:
141+
If settings are broken again:
142+
1. Check console for "An object could not be cloned" errors
143+
2. Verify all reactive refs use `.value` in save object
144+
3. Remove any `isQuitting` or validation blocks
145+
4. Use manual object mapping instead of JSON serialization
146+
147+
## Complete Technical Architecture Guide
148+
149+
### Core Architecture & Data Flow
150+
151+
**Three-Layer System:**
152+
```
153+
Vue Frontend (renderer process) ↔ Electron Main Process ↔ Python HAL Backend ↔ LinuxCNC
154+
```
155+
156+
**Communication Patterns:**
157+
- **Position Updates**: 30Hz HTTP polling via `/hal/hal_in` endpoint
158+
- **Control Commands**: Immediate HTTP PUT requests to `/hal/hal_out`
159+
- **Settings**: Electron IPC (`getSettings`/`saveSettings`) → electron-store → JSON file
160+
- **HAL Management**: IPC channels (`startHAL`, `stopHAL`, `halStarted`, `halStopped`)
161+
162+
### Key Components & Responsibilities
163+
164+
**Vue Component Hierarchy:**
165+
```
166+
App.vue (Main container - state management, menu switching, HAL coordination)
167+
├── DRODisplay.vue (Digital readout, position display, unit conversion)
168+
├── Numpad.vue (Touch-friendly numeric input with decimal handling)
169+
├── OperationPreview.vue (3D backplot using Three.js/troisjs)
170+
├── ToolTable.vue (Tool offset editing and selection)
171+
├── ParameterInput.vue (Reusable input with popover help)
172+
├── ThreadPresetSelector.vue (Threading presets dialog)
173+
├── TurningPresetSelector.vue (Turning presets dialog)
174+
└── PitchPresetSelector.vue (Manual pitch selection)
175+
```
176+
177+
### Critical APIs & Interfaces
178+
179+
**HAL REST API Endpoints (Python backend on port 8000):**
180+
```typescript
181+
// Position & Status (30Hz polling)
182+
GET /hal/hal_in → {position_x, position_z, position_a, speed_rps, program_running, error_state}
183+
184+
// Control Commands
185+
PUT /hal/hal_out → {enable_x, enable_z, forward_x, forward_z, control_source, ...}
186+
187+
// Canned Cycles
188+
PUT /hal/threadingExecute threading cycle
189+
PUT /hal/threading/generateGenerate G-code for backplot
190+
PUT /hal/turningExecute turning cycle
191+
PUT /hal/turning/generateGenerate G-code for backplot
192+
193+
// Emergency Controls
194+
PUT /hal/abortAbort current operation
195+
PUT /hal/estopEmergency stop
196+
PUT /hal/cleanupClean up after cycles
197+
```
198+
199+
**Electron IPC Channels:**
200+
```typescript
201+
// HAL Management
202+
send('startHAL') → Start LinuxCNC
203+
send('stopHAL') → Stop LinuxCNC
204+
receive('halStarted') → HAL ready signal
205+
receive('halStopped') → HAL shutdown signal
206+
207+
// Settings Persistence
208+
invoke('getSettings') → Load all settings
209+
invoke('saveSettings', data) → Save all settings
210+
```
211+
212+
**Key Data Structures:**
213+
```typescript
214+
interface Tool {
215+
id: number
216+
offsetX: number // X-axis tool offset
217+
offsetZ: number // Z-axis tool offset
218+
description: string
219+
}
220+
221+
interface HalIn {
222+
position_z: number
223+
position_x: number
224+
position_a: number // Spindle angle
225+
speed_rps: number // Spindle speed
226+
program_running: boolean
227+
error_state: boolean
228+
}
229+
```
230+
231+
### State Management Architecture
232+
233+
**Composable-Based State (No Vuex/Pinia):**
234+
- **useHAL()**: Real-time position data, HAL communication, jog controls
235+
- **useSettings()**: Persistent settings, unit conversion, tool management
236+
- **useCannedCycles()**: Threading/turning parameters, validation, G-code generation
237+
- **useToolTable()**: Tool offset management and selection
238+
239+
**Position Calculation System:**
240+
```typescript
241+
// Work coordinates = Machine coordinates - Axis offsets + Tool offsets
242+
displayPosition = machinePosition - axisOffset + toolOffset
243+
244+
// Diameter mode: X display = X radius × 2
245+
displayXPos = diameterMode.value ? xpos.value * 2 : xpos.value
246+
247+
// Tool offset application (in useHAL startPoll):
248+
zpos.value = (halIn as any).position_z - zaxisoffset + toolOffsets.currentToolOffsetZ.value
249+
xpos.value = (halIn as any).position_x - xaxisoffset + toolOffsets.currentToolOffsetX.value
250+
```
251+
252+
**Coordinate System Management:**
253+
- Single work coordinate system shared between manual and canned cycle modes
254+
- Axis offsets managed in HAL composable (`xaxisoffset`, `zaxisoffset`, `aaxisoffset`)
255+
- Tool offsets applied separately per tool in position calculations
256+
- Reset position functionality zeroes work coordinates
257+
258+
### Build & Development Workflow
259+
260+
**Key Scripts:**
261+
```bash
262+
# Development
263+
yarn start # Start frontend dev server + electron app + HAL
264+
yarn serve:front # Frontend dev server only (port 3000)
265+
yarn watch # Watch electron main process
266+
267+
# Production
268+
yarn build # Compile TypeScript
269+
yarn build:front # Build Vue frontend
270+
yarn app:build # Full production build with electron-builder
271+
```
272+
273+
**Development vs Production:**
274+
- **Dev**: Frontend from Vite dev server (localhost:3000), devTools enabled
275+
- **Prod**: Frontend from `file://` protocol, devTools disabled
276+
- **HAL URLs**: Auto-detect Electron vs browser for remote development
277+
278+
### Common Coding Patterns
279+
280+
**Component Pattern:**
281+
```vue
282+
<script setup lang="ts">
283+
// 1. Composable imports at top
284+
import { useHAL } from './composables/useHAL'
285+
import { useSettings } from './composables/useSettings'
286+
287+
// 2. Destructure reactive refs
288+
const { xpos, zpos, startPoll } = useHAL()
289+
const { metric, diameterMode } = useSettings()
290+
291+
// 3. Computed properties for display
292+
const displayValue = computed(() =>
293+
metric.value ? value : value / 25.4
294+
)
295+
</script>
296+
```
297+
298+
**Unit Conversion Pattern:**
299+
```typescript
300+
// Consistent conversion factor usage
301+
const conversionFactor = toMetric ? 25.4 : 1 / 25.4
302+
value = roundParameterValue(value, conversionFactor)
303+
304+
// Display formatting
305+
const formatValue = (value: number) =>
306+
metric.value ? value.toFixed(3) : (value / 25.4).toFixed(4)
307+
```
308+
309+
**Error Handling Pattern:**
310+
```typescript
311+
// HAL API calls - graceful degradation
312+
try {
313+
const response = await fetch(url, options)
314+
const result = await response.json()
315+
return result
316+
} catch {
317+
return {} // Silent failure, return empty object
318+
}
319+
```
320+
321+
### Adding New Features - Standard Process
322+
323+
1. **Add state to appropriate composable** (useHAL, useSettings, useCannedCycles)
324+
2. **Add persistence to useSettings** if needed:
325+
- Add ref definition
326+
- Add to save object with manual serialization
327+
- Add to return statement
328+
- Add to watcher array
329+
- Update main.ts IPC handlers
330+
- Update TypeScript interfaces
331+
3. **Add validation functions** for user inputs
332+
4. **Add unit conversion support** for metric/imperial display
333+
5. **Create UI component** following existing PrimeVue patterns
334+
6. **Add to main App.vue** with proper event handling
335+
336+
### Critical File Locations
337+
338+
**Frontend:**
339+
- `elle-frontend/src/App.vue` - Main application container
340+
- `elle-frontend/src/composables/useSettings.ts` - Settings management
341+
- `elle-frontend/src/composables/useHAL.ts` - HAL communication
342+
- `elle-frontend/src/components/` - Vue components
343+
344+
**Electron:**
345+
- `elle-electron/main.ts` - Main process, IPC handlers
346+
- `elle-electron/electron-store/configuration.ts` - Settings schema
347+
- `elle-electron/preload.d.ts` - TypeScript definitions
348+
349+
**Backend:**
350+
- `elle-hal/lathe_halcomp.py` - Python Flask HAL interface
351+
- `elle-hal/elle-lathe.ini` - LinuxCNC configuration
352+
353+
### Debugging Tips
354+
355+
**Settings Issues:**
356+
- Check `~/.config/elle-app/appConfig.json` for saved data
357+
- Console errors about "object could not be cloned" = serialization issue
358+
- Look for Vue refs passed without `.value`
359+
360+
**HAL Communication Issues:**
361+
- Check Python backend is running on port 8000
362+
- Verify HAL pins are created: `halcmd show pin`
363+
- Check for 400 errors in network tab = invalid HAL requests
364+
365+
**Position Calculation Issues:**
366+
- Tool offsets applied in `useHAL.ts` `startPoll` function
367+
- Axis offsets managed separately from tool offsets
368+
- Diameter mode doubles X display only, not actual position

elle-app/elle-electron/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ ipcMain.handle('getSettings', () => ({
8888
currentToolOffsetZ: (appConfig as any).get('setting.currentToolOffsetZ', 0)
8989
}))
9090

91+
// IMPORTANT: This handler should save ALL settings passed from useSettings.ts
92+
// If you add new settings, make sure they're included both here AND in useSettings.ts
93+
// Do NOT create duplicate save logic elsewhere - use the useSettings saveSettings function
9194
ipcMain.handle('saveSettings', (event, settings) => {
9295
const currentSettings = (appConfig as any).get('setting', {})
9396
;(appConfig as any).set('setting', {

elle-app/elle-frontend/src/App.vue

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ const xpitchlabel = ref('…')
178178
const zpitchlabel = ref('')
179179
const xpitchangle = ref(0)
180180
// Get settings from composable
181-
const { metric, diameterMode, defaultMetricOnStartup, selectedThreadingTab, selectedTurningTab, selectedPitchTab, pitchX, pitchZ, isQuitting, loadSettings, tools, currentToolIndex, currentToolOffsetX, currentToolOffsetZ } = useSettings()
181+
const { metric, diameterMode, defaultMetricOnStartup, selectedThreadingTab, selectedTurningTab, selectedPitchTab, pitchX, pitchZ, isQuitting, loadSettings, saveSettings, tools, currentToolIndex, currentToolOffsetX, currentToolOffsetZ } = useSettings()
182182
183183
const cursorpos = ref(0)
184184
@@ -653,17 +653,9 @@ const quitApplication = async () => {
653653
if (userAgent.indexOf(' electron/') > -1) {
654654
isQuitting.value = true
655655
656-
// Save settings one final time before quitting
656+
// Save settings one final time before quitting using the proper save function
657657
try {
658-
await window.settings.save({
659-
diameterMode: diameterMode.value,
660-
defaultMetricOnStartup: defaultMetricOnStartup.value,
661-
selectedThreadingTab: selectedThreadingTab.value,
662-
selectedTurningTab: selectedTurningTab.value,
663-
selectedPitchTab: selectedPitchTab.value,
664-
pitchX: pitchX.value,
665-
pitchZ: pitchZ.value
666-
})
658+
await saveSettings()
667659
} catch (error) {
668660
console.error('Failed to save final settings:', error)
669661
}

elle-app/elle-frontend/src/composables/useSettings.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,27 +83,30 @@ export function useSettings() {
8383
}
8484

8585
const saveSettings = async () => {
86-
if (isQuitting.value) {return} // Don't save settings when quitting
87-
8886
const userAgent = navigator.userAgent.toLowerCase()
8987
if (userAgent.indexOf(' electron/') > -1) {
9088
try {
91-
// Convert reactive arrays to plain objects for serialization
92-
const plainTools = JSON.parse(JSON.stringify(tools.value))
93-
94-
await window.settings.save({
89+
// Convert reactive refs to plain values for serialization
90+
const settingsToSave = {
9591
diameterMode: diameterMode.value,
9692
defaultMetricOnStartup: defaultMetricOnStartup.value,
9793
selectedThreadingTab: selectedThreadingTab.value,
9894
selectedTurningTab: selectedTurningTab.value,
99-
selectedPitchTab: selectedPitchTab.value,
95+
selectedPitchTab: [...selectedPitchTab.value],
10096
pitchX: pitchX.value,
10197
pitchZ: pitchZ.value,
102-
tools: plainTools,
98+
tools: tools.value.map(tool => ({
99+
id: tool.id,
100+
offsetX: tool.offsetX,
101+
offsetZ: tool.offsetZ,
102+
description: tool.description
103+
})),
103104
currentToolIndex: currentToolIndex.value,
104105
currentToolOffsetX: currentToolOffsetX.value,
105106
currentToolOffsetZ: currentToolOffsetZ.value
106-
})
107+
}
108+
109+
await window.settings.save(settingsToSave)
107110
} catch (error) {
108111
console.error('Failed to save settings:', error)
109112
}

0 commit comments

Comments
 (0)