# Defensive Guard Fix - Undefined 'original' Property

**Date:** 2025-11-23  
**Issue:** TypeError: Cannot read properties of undefined (reading 'original')  
**Location:** `useRouteOptimization.ts:21:58`  
**Status:** ✅ FIXED

---

## Error Analysis

### Stack Trace
```
ErrorBoundary caught: TypeError: Cannot read properties of undefined (reading 'original')
    at ComputedRefImpl.fn (useRouteOptimization.ts:21:58)
    at RouteOptimizerDialog.vue:59:46
```

### Root Cause
The `savingsPercentage` computed property accessed `optimizationResult.value.comparison.original` without checking if `comparison` and `original` properties exist:

```typescript
// ❌ BEFORE (line 19-25)
const savingsPercentage = computed(() => {
  if (!optimizationResult.value) return 0
  const original = optimizationResult.value.comparison.original.totalDistanceKm
  //                                       ^^^^^^^^^^ undefined access
  const optimized = optimizationResult.value.comparison.optimized.totalDistanceKm
  if (original === 0) return 0
  return Math.round(((original - optimized) / original) * 100)
})
```

**Problem:** The check `if (!optimizationResult.value)` only validates that the object exists, but doesn't validate the nested `comparison.original` structure.

---

## Solution Implemented

### Added Optional Chaining Guards

```typescript
// ✅ AFTER (defensive guards)
const savingsPercentage = computed(() => {
  if (!optimizationResult.value?.comparison?.original || 
      !optimizationResult.value?.comparison?.optimized) {
    return 0
  }
  const original = optimizationResult.value.comparison.original.totalDistanceKm
  const optimized = optimizationResult.value.comparison.optimized.totalDistanceKm
  if (original === 0) return 0
  return Math.round(((original - optimized) / original) * 100)
})
```

**Changes:**
- Added `?.comparison?.original` optional chaining
- Added `?.comparison?.optimized` optional chaining
- Returns `0` safely if structure is incomplete

---

## Why This Happened

### Timing Issue
1. User opens `RouteOptimizerDialog`
2. Vue renders template with `{{ savingsPercentage }}`
3. `savingsPercentage` computed is evaluated
4. `optimizationResult.value` is `null` initially (before API call)
5. After API call, `optimizationResult.value` is set
6. **BUT**: During render, if response is partial or malformed, crash occurs

### Expected vs Actual

| State | Expected | Actual (before fix) |
|-------|----------|---------------------|
| Initial | `optimizationResult.value = null` | ✅ Check passed |
| After API | `optimizationResult.value = { comparison: { original, optimized } }` | ✅ Works |
| **Malformed response** | Should handle gracefully | ❌ **CRASH** - `comparison` undefined |

---

## Type Safety

### TypeScript Type Definition
```typescript
// packages/types/src/routeOptimization.types.ts
export interface OptimizationResult {
  originalOrder: string[]
  optimizedOrder: string[]
  savings: { ... }
  comparison: {
    original: RouteMetrics   // Should always exist
    optimized: RouteMetrics  // Should always exist
  }
  alternatives: RouteAlternative[]
}
```

**Issue:** TypeScript assumes `comparison` always exists, but runtime can have:
- Network errors returning partial JSON
- Backend bugs returning incomplete data
- Race conditions during state updates

**Solution:** Runtime guards with optional chaining (`?.`)

---

## Files Modified

| File | Lines Changed | Change Type |
|------|---------------|-------------|
| `apps/web/src/modules/routeOptimization/composables/useRouteOptimization.ts` | 20-21 | Added defensive guards |

**Diff:**
```diff
  const savingsPercentage = computed(() => {
-   if (!optimizationResult.value) return 0
+   if (!optimizationResult.value?.comparison?.original || 
+       !optimizationResult.value?.comparison?.optimized) {
+     return 0
+   }
    const original = optimizationResult.value.comparison.original.totalDistanceKm
    const optimized = optimizationResult.value.comparison.optimized.totalDistanceKm
```

---

## Related Code (Already Safe)

### RouteOptimizerDialog.vue
```typescript
// Line 168 - Already has defensive guard ✅
const savings = computed(() => {
  return optimizationResult.value?.savings || {
    distanceKm: 0,
    durationHours: 0,
    fuelLiters: 0,
    costEur: 0,
    co2Kg: 0,
  }
})
```

**Note:** `savings` computed was already safe. Only `savingsPercentage` was vulnerable.

---

## Validation

### Type Check
```bash
pnpm type-check
```
**Result:** ✅ PASS (6 tasks, 2.3s, 5 cached)

### Lint Check
```bash
pnpm lint
```
**Result:** ✅ PASS

### Manual Test
1. Open Route Optimizer dialog
2. Click "Optimiser"
3. Wait for API response
4. **Expected:** No crash, percentage displays correctly
5. **Before:** TypeError on `savingsPercentage` access
6. **After:** ✅ Works, displays "0%" if data incomplete

---

## Defensive Programming Patterns

### ❌ Don't Do This (Shallow Check)
```typescript
if (!obj) return defaultValue
obj.nested.property // Can crash if nested is undefined
```

### ✅ Do This (Deep Optional Chaining)
```typescript
if (!obj?.nested?.property) return defaultValue
obj.nested.property // Safe to access now
```

### Best Practices
1. **Always use optional chaining** (`?.`) for nested object access
2. **Provide fallback values** for computed properties
3. **Validate structure**, not just existence
4. **Test edge cases**: null, undefined, partial data
5. **Don't rely on TypeScript types** for runtime safety

---

## Impact Analysis

### Affected Components
- ✅ `RouteOptimizerDialog.vue` (uses `savingsPercentage`)
- ✅ `useRouteOptimization.ts` (provides computed)

### Edge Cases Now Handled
- Null/undefined `optimizationResult`
- Missing `comparison` property
- Missing `original` or `optimized` sub-properties
- Incomplete API responses
- Race conditions during state updates

### Breaking Changes
- ❌ None (purely defensive, same API)

---

## Lessons Learned

### TypeScript ≠ Runtime Safety
```typescript
// TypeScript says this is safe:
interface Result {
  comparison: { original: Metrics }
}

// But runtime can be:
const result: Result = JSON.parse(response) 
// If response is malformed, comparison might be undefined!
```

**Solution:** Always add runtime guards for:
- API responses
- JSON parsing
- User input
- External data sources

### Computed Properties Are Evaluated Eagerly
```typescript
const computed = computed(() => obj.value.nested.property)
// ⚠️ Evaluated immediately when dependencies change
// Must guard ALL nested accesses
```

---

## Related Fixes

| Module | Similar Issue | Status |
|--------|---------------|--------|
| Clan Module | ✅ Already uses `?.` for optional fields | Safe |
| Planner Module | ✅ Uses defensive guards | Safe |
| Profile Module | ✅ Validates nested objects | Safe |

**Route Optimization was the only module with this vulnerability.**

---

## Summary

**Problem:** Computed property crashed on undefined nested property access  
**Solution:** Added optional chaining guards (`?.`) with early return  
**Result:** Safe handling of incomplete data, no more crashes  
**Lines changed:** 2 lines in `useRouteOptimization.ts`  

🛡️ Production-ready with defensive programming.
