# Growth Engine Micro-Optimisation Plan

> **Philosophy:** 0.001% improvements across 100 touchpoints = 1% edge. Repeated daily = competitive advantage.

**Date:** Jan 22, 2026
**Status:** Investigation Complete - Ready for Builder

---

## Executive Summary

Comprehensive audit of the Growth Engine codebase reveals **43 specific optimization opportunities** across 7 categories. The highest-impact items are:

1. **Scalability:** Unbounded queries will crash at 50k+ leads
2. **Real-time:** No WebSocket reconnection = calls drop mid-transcription
3. **Frontend:** Missing React.memo/useCallback causing cascading re-renders
4. **Database:** 7 missing indexes + N+1 patterns in critical paths

**Estimated total effort:** 20-25 hours
**Expected impact:** Sub-second improvements across 50+ touchpoints

---

## Quick Wins Table (Do First)

| # | Fix | Effort | Impact | Category |
|---|-----|--------|--------|----------|
| 1 | Add `.limit(10000)` to dashboard queries | 15 min | CRITICAL | Scalability |
| 2 | Add missing FK indexes (assigned_to, created_at) | 15 min | HIGH | Database |
| 3 | Reduce copy feedback delay 2000ms → 500ms | 20 min | MEDIUM | UX |
| 4 | Add React.memo to StatCard | 5 min | MEDIUM | Performance |
| 5 | Add useCallback to LeadCard handlers | 10 min | MEDIUM | Performance |
| 6 | Fix O(N²) callback lookup in call ranker | 30 min | HIGH | Scalability |
| 7 | Add cursor-not-allowed to disabled buttons | 5 min | LOW | UX |
| 8 | Reduce LiveCopilot suggestion timeout 10s → 5s | 5 min | LOW | UX |

---

## 1. FRONTEND MICRO-FRICTION

### 1.1 Render Efficiency (HIGH PRIORITY)

**Problem:** 516 useState declarations but only 15 useCallback patterns (34:1 ratio)

| Component | Issue | Fix |
|-----------|-------|-----|
| `StatCard.jsx` | No React.memo | Wrap with React.memo |
| `LeadCard.jsx:28-31` | Event handlers recreated every render | Add useCallback |
| `CallOutcomeSheet.jsx:103` | handleSave recreated every render | Add useCallback |
| `LiveCopilot.jsx:387` | detectObjection not memoized | Add useCallback |

**Files to modify:**
- `admin/src/components/StatCard.jsx`
- `admin/src/components/LeadCard.jsx`
- `admin/src/components/CallOutcomeSheet.jsx`
- `admin/src/components/LiveCopilot.jsx`

### 1.2 Loading States (HIGH PRIORITY)

**Problem:** Spinners instead of skeletons cause perceived slowness

| Component | Line | Current | Target |
|-----------|------|---------|--------|
| `NewMessageModal.jsx` | 101 | Spinner | Skeleton loader |
| `AIMessageWriter.jsx` | 304 | Spinner | Skeleton preview |
| `CallOutcomeSheet.jsx` | 240 | Spinner | Placeholder text |

**Optimistic Updates Missing:**
- `AIMessageWriter.jsx:189` - Wait for API before showing success
- `CallOutcomeSheet.jsx:103` - Wait for API before closing

**Fix:** Show message in optimistic state immediately, revert on error

### 1.3 Form Efficiency (MEDIUM PRIORITY)

| Issue | Location | Fix |
|-------|----------|-----|
| No input masks on phone | Throughout | Add react-input-mask for Australian format |
| No tabIndex management | All forms | Add explicit tabIndex for keyboard navigation |
| Forms require manual tap | Most modals | Add autoFocus (some already have it) |

### 1.4 Visual Hierarchy

**Font sizes to review:**
- `text-xs` (12px) used on critical data - audit for 390px readability
- `StatCard.jsx:23` - Labels may be too small
- Target: 14px minimum for key data

---

## 2. API CALL EFFICIENCY

### 2.1 N+1 Query Patterns (HIGH PRIORITY)

| Location | Issue | Fix |
|----------|-------|-----|
| `src/routes/sequences.js:37-50` | N+1 enrollment counts per sequence | Single aggregate query |
| `src/routes/voice.js:124-138` | 3 sequential phone lookups | Single OR query |
| `src/services/learning.js:12-45` | 4 sequential queries | Promise.all() |

**Voice route fix example:**
```javascript
// BEFORE: 3 sequential queries
for (const format of formats) {
  const { data } = await supabase.from('leads').select(...).eq('phone', format);
}

// AFTER: Single OR query
const { data } = await supabase.from('leads')
  .select(...)
  .or(`phone.eq.${format1},phone.eq.${format2},phone.eq.${format3}`);
```

### 2.2 Missing Caching

| Component | Issue | Fix |
|-----------|-------|-----|
| `ManagerDashboard.jsx:38-54` | Raw fetch, no cache | Add React Query |
| `DealIntelDashboard.jsx:24-31` | Raw API calls | Add React Query |
| `buildRankingContext()` | Called multiple times | Add 5-min in-memory cache |

### 2.3 Request Patterns

**Missing AbortController:** No request cancellation on unmount
- Add AbortController to LiveCopilot fetch operations
- Add cleanup in useEffect return

---

## 3. DATABASE MICRO-OPTIMISATION

### 3.1 Missing Indexes (HIGH PRIORITY)

```sql
-- Critical indexes to add
CREATE INDEX IF NOT EXISTS idx_leads_assigned_to ON leads(assigned_to);
CREATE INDEX IF NOT EXISTS idx_leads_created_at ON leads(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_leads_last_contact_at ON leads(last_contact_at DESC);
CREATE INDEX IF NOT EXISTS idx_communications_created_at ON communications(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_communications_handled_by ON communications(handled_by);
CREATE INDEX IF NOT EXISTS idx_callbacks_status_scheduled ON callbacks(status, scheduled_at);
```

### 3.2 SELECT * Usage

| File | Line | Fix |
|------|------|-----|
| `src/services/learning.js` | 17, 29, 36, 43 | Specify columns |
| `src/routes/calls.js` | 60 | Specify needed columns only |
| `src/jobs/dailySummary.js` | 40-91 | Use `head: true` for counts |
| `src/services/dealIntelligence.js` | 19, 213, 273 | Specify columns |

### 3.3 LIKE Search Pattern

**File:** `src/routes/leads.js:82`

**Problem:** `%search%` pattern prevents index use

**Options:**
1. **Quick fix:** Change to prefix search `search%` (uses index)
2. **Better:** Add PostgreSQL Full-Text Search with GIN index

```sql
-- Add text search column
ALTER TABLE leads ADD COLUMN search_text tsvector GENERATED ALWAYS AS (
  to_tsvector('english',
    coalesce(first_name, '') || ' ' ||
    coalesce(last_name, '') || ' ' ||
    coalesce(email, '') || ' ' ||
    coalesce(company, '')
  )
) STORED;

CREATE INDEX idx_leads_search ON leads USING GIN(search_text);
```

---

## 4. REAL-TIME PERFORMANCE

### 4.1 WebSocket Reconnection (CRITICAL)

**File:** `admin/src/components/LiveCopilot.jsx:605-892`

**Problem:** No auto-reconnect if transcription socket drops mid-call

**Current:**
```javascript
socket.onclose = () => {
  setIsListening(false);  // Just sets state, no retry
};
```

**Fix:** Add exponential backoff reconnection
```javascript
let retries = 0;
const maxRetries = 3;

socket.onclose = () => {
  if (retries < maxRetries && isListening) {
    setTimeout(() => {
      retries++;
      connectWebSocket();  // Retry
    }, Math.pow(2, retries) * 1000);  // 1s, 2s, 4s backoff
  }
};
```

### 4.2 Supabase Realtime Scoping

**File:** `admin/src/context/RealtimeContext.jsx:167-326`

**Problem:** Wide subscriptions - listens to ALL table events, not filtered by user

**Impact:** 50+ users = 50+ duplicate event notifications

**Fix:** Add row-level filtering or use presence channels

### 4.3 Optimistic Updates Missing

**Problem:** UI waits for full API round-trip

| Component | Current | Target |
|-----------|---------|--------|
| `BulkSendModal.jsx` | onSuccess invalidates cache | onMutate updates cache optimistically |
| `AIMessageWriter.jsx` | Wait for send API | Show message immediately, rollback on error |
| `Messages.jsx` | Invalidate after send | Optimistic message + rollback |

---

## 5. TIMING MICRO-DELAYS

### 5.1 Copy Feedback Too Long (MEDIUM)

**Current:** 1500-2000ms across 8+ components

| File | Line | Current | Target |
|------|------|---------|--------|
| `AIRecommendationCard.jsx` | 43 | 2000ms | 500ms |
| `CallSalesFunnel.jsx` | 152 | 1500ms | 500ms |
| `EliteCallUI.jsx` | 158 | 1500ms | 500ms |
| `ScriptPanel.jsx` | 27 | 1500ms | 500ms |
| `ScriptDetail.jsx` | 67 | 2000ms | 500ms |
| `Objections.jsx` | 52 | 2000ms | 500ms |
| `PriorityActionCard.jsx` | 99, 113 | 2000ms | 500ms |
| `CallPrepPage.jsx` | 291 | 2000ms | 500ms |

### 5.2 Toast Auto-Dismiss

| File | Line | Current | Target | Reason |
|------|------|---------|--------|--------|
| `LiveCopilot.jsx` | 175 | 10000ms | 5000ms | Too long |
| `LiveCopilot.jsx` | 443 | 8000ms | 5000ms | Too long |
| `CallOutcomeSheet.jsx` | 163 | 1500ms | 800ms | XP celebration delay |

### 5.3 Debounce (Optimal)

- `SearchBar.jsx:13` - 300ms debounce (GOOD - keep)

---

## 6. RELIABILITY

### 6.1 XP Race Condition (PENDING)

**File:** `src/routes/xp.js:137-150`

**Status:** Migration pending for atomic increment function

**Fix:** Apply `supabase/xp-atomic-migration.sql`

### 6.2 Error Handling Gaps

| Issue | Location | Fix |
|-------|----------|-----|
| Promise.all no partial failure | Dashboard, AI routes | Use Promise.allSettled() |
| No retry on mutation failure | BulkSendModal | Add onError with retry |
| Failed jobs not retried | scheduledSms.js | Add exponential backoff |

### 6.3 Missing External Monitoring

**Problem:** Errors only in Railway logs, not indexed

**Recommendation:** Add Sentry integration for error tracking

---

## 7. SCALABILITY FLAGS

### 7.1 Unbounded Queries (CRITICAL)

| File | Line | Issue | Breaking Point |
|------|------|-------|----------------|
| `src/routes/dashboard.js` | 229-261 | Fetches ALL leads | 50k leads |
| `src/routes/analytics.js` | 158-210 | ALL leads to memory | 25k leads |
| `src/services/learning.js` | 26-45 | Sequential queries | 10 concurrent |

**Fix:** Add `.limit(10000)` to all unbounded queries

### 7.2 O(N²) Call List Ranking (HIGH)

**File:** `src/services/callListRanker.js:375-390`

**Problem:** For each lead, loops through ALL callbacks

**Breaking Point:** 500 leads × 1000 callbacks = 10s response

**Fix:** Index callbacks by lead_id using Map for O(1) lookup

```javascript
// BEFORE
for (const lead of leads) {
  const leadCallbacks = callbacks.filter(cb => cb.lead_id === lead.id);
}

// AFTER
const callbackMap = new Map();
callbacks.forEach(cb => {
  if (!callbackMap.has(cb.lead_id)) callbackMap.set(cb.lead_id, []);
  callbackMap.get(cb.lead_id).push(cb);
});

for (const lead of leads) {
  const leadCallbacks = callbackMap.get(lead.id) || [];
}
```

### 7.3 Platform Sync Loop Inserts

**File:** `src/jobs/platformSync.js:59-132`

**Problem:** Sequential inserts for each activity

**Fix:** Batch upsert with 100-item chunks

### 7.4 Memory Leak

**File:** `src/services/criticalAlerts.js:29`

**Problem:** `recentAlerts` Map never cleared

**Fix:** Add TTL cleanup (delete entries older than 24h)

---

## 8. KEYBOARD & ACCESSIBILITY

### 8.1 Missing Shortcuts

**Current shortcuts:**
- Enter = send (Messages)
- Arrow keys = navigate leads (SwipeableLeadProfile)
- Escape = close modal (various)

**Missing:**
- Ctrl+K = search/command palette
- Ctrl+S = save
- N = next lead
- Space = start/end call

### 8.2 Focus Management

**Gaps:**
- No focus trap in modals (Tab can escape)
- No focus restoration after modal close
- Missing aria-label on 50+ icon-only buttons

### 8.3 ARIA Labels Needed

**Files with icon-only buttons missing aria-label:**
- `Messages.jsx:875-880` - Smart Reply button
- `Messages.jsx:893-900` - Schedule button
- Throughout - many icon buttons

---

## Implementation Priority

### Phase 1: Critical Fixes (4-6 hours)

1. [x] Add `.limit(10000)` to unbounded dashboard queries
2. [x] Add 7 missing database indexes
3. [x] Fix O(N²) callback lookup in call ranker
4. [x] Add WebSocket reconnection to LiveCopilot
5. [x] Apply XP atomic increment migration

### Phase 2: Performance Quick Wins (2-3 hours)

6. [x] Add React.memo to StatCard
7. [x] Add useCallback to LeadCard, CallOutcomeSheet
8. [x] Reduce copy feedback delays (2000ms → 500ms)
9. [x] Reduce LiveCopilot toast durations
10. [x] Add React Query to ManagerDashboard, DealIntelDashboard

### Phase 3: API Efficiency (3-4 hours)

11. [x] Fix N+1 in sequences.js (enrollment counts)
12. [x] Fix N+1 in voice.js (phone lookups)
13. [x] Parallelize learning.js queries
14. [x] Add caching to buildRankingContext()

### Phase 4: Database Optimization (2-3 hours)

15. [x] Replace SELECT * with specific columns (calls.js, dealIntelligence.js)
16. [x] Add Full-Text Search for leads (migration applied)
17. [x] Denormalize outcome/duration from communications.metadata (migration applied)

### Phase 5: UX Polish (3-4 hours)

18. [x] Add skeleton loaders to replace spinners (Skeleton.jsx component)
19. [x] Implement optimistic updates for SMS sending (Messages.jsx)
20. [x] Add input masks for phone numbers (WorkerReferralCapture.jsx)
21. [x] Add aria-labels to icon buttons (LeadCard.jsx)
22. [x] Add modal focus trap (useFocusTrap hook + NewMessageModal)

### Phase 6: Reliability (2-3 hours)

23. [x] Add Promise.allSettled for parallel operations (ai.js, dashboard.js, learning.js)
24. [x] Add retry logic for failed mutations (scheduledSms.js with exponential backoff)
25. [x] Add Map cleanup to criticalAlerts (periodic hourly cleanup)
26. [x] Skip Sentry integration (optional - requires external setup)

---

## Success Metrics

After implementation, measure:

| Metric | Current | Target |
|--------|---------|--------|
| Dashboard load time | ~3s | <1s |
| Call list generation | ~2s | <500ms |
| Click-to-feedback latency | ~200ms | <50ms |
| Copy button reset | 2000ms | 500ms |
| WebSocket reconnection | Never | Auto 3x |
| Scalability ceiling | 50k leads | 500k leads |

---

## Technical Debt Flags

Things that work now but will bite at scale:

| Issue | Risk Level | When It Breaks | Fix Complexity |
|-------|------------|----------------|----------------|
| No pagination on analytics | HIGH | 25k+ leads | Medium |
| Wide Supabase subscriptions | MEDIUM | 50+ users | High |
| In-memory callback filtering | HIGH | 1k+ callbacks | Low |
| Platform sync sequential | MEDIUM | 500+ activities | Medium |
| No external monitoring | HIGH | Any production error | Low |

---

## Notes for Builder

1. **Test scalability fixes** with large datasets (seed 10k leads for testing)
2. **Measure before/after** for each change
3. **Don't break existing behavior** - all changes should be additive
4. **Push after each phase** - don't batch all changes

## Notes for QA

1. **Test dashboard load time** before/after index additions
2. **Test call list** with 500+ leads
3. **Verify WebSocket reconnection** by simulating network drop
4. **Test on mobile** - 390px viewport for font sizes
5. **Check copy feedback** timing feels snappy

---

**Plan created by:** Architect
**Ready for:** Builder
