# 🔨 Post-Launch Optimization Review — 26 Feb 2026
## Comprehensive Performance, DB, Mobile & Error Monitoring Audit

**Builds on:** `launch-audit-2026-02-26.md` (security/code) + `perf-audit-2026-02-26.md` (server/bundle)  
**This review adds:** Database query analysis, mobile responsiveness audit, error monitoring gap analysis, next-sprint priorities

---

## 1. Database Query Optimization

### Current State
- **13 tables**, all with RLS ✅
- **56 indexes** across all tables — well-covered
- **Row counts:** 25 profiles, 16 jobs, 14 worker profiles, 5 matches, 5 companies — pre-launch scale
- **Matching engine:** `find_matching_workers()` is a 5,231-char PL/pgSQL function running entirely DB-side with CTE-based scoring. Efficient design.

### Index Coverage Assessment

| Query Pattern | Index Exists | Rating |
|---------------|-------------|--------|
| Jobs by status + created_at | ✅ Separate indexes | 🟡 Composite would be faster |
| Matches by job_id | ✅ `idx_matches_job_id` | ✅ |
| Matches by worker_id | ✅ `idx_matches_worker_id` | ✅ |
| Matches by job_id + worker_id | ✅ Unique constraint | ✅ |
| Notifications by profile_id + read | ✅ Composite `idx_notifications_read` | ✅ |
| Worker profiles by trades (GIN) | ✅ `idx_worker_profiles_trades` | ✅ |
| Profiles by location (GiST) | ✅ `idx_profiles_location` | ✅ |

**Good news:** Index coverage is comprehensive. The `find_matching_workers()` RPC handles the most complex query pattern DB-side, avoiding N+1 in the matching flow.

### Dashboard Waterfall Issue
**File:** `src/app/(authenticated)/dashboard/page.tsx`  
**Issue:** Makes **8 sequential `await` calls** to Supabase (profile → company → jobCount → matchCount → contactCount → workerProfile → hiredMatches → workerHires). Only one `Promise.all` for ratings.

**Impact:** At current scale (25 rows), ~200ms total. At 10K users, this waterfalls to 800ms+.

**Recommended Fix:**
```typescript
// Parallelize independent queries
const [profile, company, workerProfile] = await Promise.all([
  supabase.from("profiles").select("...").eq("id", user.id).single(),
  supabase.from("companies").select("...").eq("profile_id", user.id).single(),
  supabase.from("worker_profiles").select("...").eq("profile_id", user.id).single(),
]);
```

**Priority:** P3 (post-launch) — no impact at current scale.

### Missing Composite Indexes (Scale Preparation)

```sql
-- Jobs filtered by status + ordered by created_at (worker jobs page)
CREATE INDEX idx_jobs_status_created ON jobs(status, created_at DESC);

-- Matches filtered by job_id + contractor_action (matches page excludes rejected)
CREATE INDEX idx_matches_job_action ON matches(job_id, contractor_action);

-- Payments by match_id + status (webhook lookup)
CREATE INDEX idx_payments_match_status ON payments(match_id, status);
```

**Priority:** P3 — only needed at 1K+ concurrent users.

---

## 2. Mobile Responsiveness Audit

### Touch Targets ✅ Excellent
- **225 instances** of `h-12`, `h-14`, `h-16`, `min-h-[44px]` — Apple's 44pt minimum consistently applied
- All CTAs are full-width (`w-full`) with minimum 48px height
- Swipe gestures on jobs and matches pages use `touch-pan-y` correctly

### Safe Area Handling ✅ Good
- **7 components** use `pb-[env(safe-area-inset-bottom)]` for notch/home-bar avoidance
- Bottom nav, job detail sheet, message input, profile save bars — all covered
- Fixed CTAs properly account for bottom nav offset (`bottom-16` where nav exists)

### Layout Containment ✅ Good
- `max-w-screen-sm` applied consistently (66 instances) — content stays readable on tablets
- `overflow-x-auto` with `scrollbar-hide` on trade filters and job selectors (13 instances)
- No horizontal overflow issues detected

### Viewport Configuration ✅
```typescript
export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  maximumScale: 1,      // Prevents zoom (appropriate for app-like PWA)
  themeColor: "#16a34a",
};
```

### Mobile Issues Found

**Issue 1: `text-xs` Overuse**  
76 instances of `text-xs` (12px) in authenticated pages. On 375px screens with varying DPI, this is borderline readable for older construction workers. Particularly in:
- Notification body text
- Help text under form fields
- Badge labels in match cards

**Recommendation:** Audit and promote critical `text-xs` to `text-sm` (14px) where the text conveys important info (notification bodies, match details).

**Issue 2: `maximumScale: 1` Blocks Pinch-to-Zoom**  
While appropriate for an app-like experience, this is an accessibility concern. Users with vision impairments can't zoom. Consider removing `maximumScale: 1` and handling zoom disruption with `touch-action: manipulation` on interactive elements instead.

**Issue 3: Bottom Sheet Z-Index Stack**  
Worker jobs page uses `z-[60]` for detail sheet and `z-[70]` for sticky CTA inside it. The bottom nav is `z-50`. This works but is fragile — any new modal or overlay needs careful z-index management. Consider a z-index system:
```typescript
// lib/constants.ts
export const Z = { nav: 50, sheet: 60, sheetCta: 70, modal: 80, toast: 90 };
```

---

## 3. Error Monitoring Setup

### Current State: ⚠️ GAP

| Capability | Status | Notes |
|------------|--------|-------|
| Structured logging | ✅ pino | Production: warn/error only |
| Client-side error boundaries | ✅ Present | At authenticated, contractor, worker levels |
| Server-side error handling | ✅ try/catch in all API routes | Consistent pattern |
| External error tracking (Sentry/etc) | ❌ **Missing** | No APM, no alerting |
| Uptime monitoring | ❌ **Missing** | No `/api/health`, no external ping |
| Client-side error reporting | ❌ **Missing** | Errors stay in user's console |
| Performance monitoring | ❌ **Missing** | No Web Vitals tracking |

### What Happens When Things Break (Current)
1. Server error → logged to journalctl via pino → nobody sees it unless they SSH in
2. Client error → caught by error boundary → user sees "Something went wrong" → no report sent
3. Stripe webhook fails → Stripe retries (good) → but no alert to team
4. App goes down → nobody knows until a user complains

### Recommended Setup (2 Hours Total)

**Step 1: Health Endpoint (10 min)** — from launch audit P0 #2
```typescript
// src/app/api/health/route.ts
export async function GET() {
  return Response.json({ status: 'ok', timestamp: new Date().toISOString() });
}
```

**Step 2: Uptime Monitoring (10 min)**  
BetterStack free tier or UptimeRobot — ping `/api/health` every 60 seconds, alert via Telegram/email.

**Step 3: Sentry Free Tier (30 min)**  
- Captures client + server errors automatically
- Source maps for readable stack traces
- 5K events/month free — more than enough for launch
- Next.js SDK has first-class support

**Step 4: Web Vitals to Analytics (15 min)**  
Add `reportWebVitals` to layout to track LCP, FID, CLS on real user devices.

---

## 4. Top 3 Next-Sprint Priorities

### Priority 1: Fix Payment Race Condition + Add Health Endpoint
**From:** Launch audit P0 #1 + P0 #2  
**Effort:** 1 hour  
**Impact:** Prevents data corruption under load + enables uptime monitoring  
**Why first:** This is the only issue that could cause real financial damage to users.

### Priority 2: Add Sentry + Uptime Monitoring
**From:** Error monitoring gap analysis above  
**Effort:** 45 minutes  
**Impact:** Know when things break before users tell you  
**Why second:** Launching without observability is flying blind. Free tier covers everything needed.

### Priority 3: Lazy-Load react-pdf + Enable Notification Polling
**From:** Perf audit + launch audit P1 #4  
**Effort:** 1 hour  
**Impact:** 300KB bundle reduction on all pages except onboarding-pack + workers see hires in real-time  
**Why third:** Biggest UX improvement per effort. Workers currently have to refresh to see "You're hired!"

### Honourable Mentions (Sprint After)
- Dashboard query parallelization (P3)
- Welcome emails on signup (P2, 15 min)
- CSRF on remaining 3 unprotected endpoints (P1, 15 min)
- Promote `text-xs` to `text-sm` on critical mobile text (P3, 30 min)

---

## Summary

| Area | Grade | Notes |
|------|-------|-------|
| Database | **A** | Excellent index coverage, DB-side matching, RLS everywhere |
| Mobile UX | **A-** | Touch targets, safe areas, swipe gestures all solid. Minor text-size concerns |
| Error Monitoring | **D** | Logging exists but no external tracking, no alerting, no uptime |
| Server Performance | **A** | Sub-100ms TTFB, 123MB memory, stable |
| Bundle | **B** | 2.4MB total, react-pdf dominates unnecessarily |

**Overall: B+** — The app is well-built. The single biggest gap is observability (no Sentry, no uptime monitoring). Fix that + the payment race condition and this is a solid production system.

---

*Three audits. One codebase. Ready to ship. 🔨*
