Troubleshooting

Top issues and exact fixes

The most common problems we’ve seen in production, with the steps to fix each one.

I can't log in

Wrong password, account doesn't exist, OAuth state mismatch, or session blacklisted.

Try these in order:

  1. Confirm your email is correct — typos happen. Pick from your browser autofill.
  2. Try password reset at /auth/forgot-password. If the reset email arrives, your password is wrong; reset it.
  3. If reset email doesn't arrive within 5 min: check spam. If still nothing, the email isn't registered. Use a different email or contact support.
  4. OAuth state mismatch error? You see ?error=google_state_mismatch in the URL. Clear cookies for trainer.evofit.io and try again.
  5. OAuth state cookie blocked? Some privacy browsers (Brave strict mode) block third-party cookies. Try in Chrome or Safari.
  6. Got locked out after multiple failures? No account-level lockout exists today, but rate-limiting may trigger. Wait 5 minutes and retry.

If absolutely none of the above works: email support@evofit.io with the email address you registered with. We can confirm whether the account exists and reset on your behalf.

My dashboard is blank or showing errors

Hard refresh, check tier status, look at console errors.

Steps:

  1. Hard refresh — Cmd-Shift-R (Mac) / Ctrl-Shift-R (Win/Linux). Service Worker may be serving a stale build.
  2. Check tier status — if you're a trainer past free-trial, dashboard is paywalled. See Free trial expiry FAQ.
  3. Open browser DevTools (F12 or right-click → Inspect → Console). Look for red errors.
  4. Common errors and what they mean:
  • 401 Unauthorized → your session expired. Log out and back in.
  • 402 TRIAL_EXPIRED → pay any tier at /pricing.
  • 404 Not Found on /api/clients/[id] → the route bug fixed 2026-04-27. Hard-refresh.
  • 500 Internal Server Error → file a bug via Report Bug button.
  1. Try a different browser to isolate browser-extension interference.
  2. Clear site data — DevTools → Application → Storage → Clear site data. Last resort, will require re-login.

If nothing works: file a bug. Include browser console screenshot.

My client never received the invite email

Spam, wrong email, Mailgun rejected.

Diagnose:

  1. Ask the client to check spam. Most common.
  2. Confirm you typed the right email. Open /clients → under Pending filter, find the row → double-check email.
  3. Did the invite POST return success? When you clicked Send, did you see the green toast? If you saw a red error toast, Mailgun rejected the send (502). Resend.
  4. Test with your own email. Send an invite to yourself. If you receive it, your client's mail provider is filtering us. They need to whitelist mg.trainer.evofit.io.
  5. Cancel and re-send. Sometimes a fresh send works.

Mailgun health:

  • Migrated to fresh domain mg.trainer.evofit.io on 2026-04-23.
  • Pre-2026-04-22 there was a silent 401 (Mailgun key disabled). The send route now returns 502 on rejection.
  • If 502 errors are happening on your account, file a bug — we'll check Mailgun event logs.

If client truly cannot receive: Use Add Existing Client instead — works without email if they create their own account first.

My workout won't save / log won't post

Offline mode queues writes — they sync when online. Check SyncStatus.

Likely cause: you're offline. EvoFit queues writes locally and syncs when back online — by design.

Steps:

  1. Look at the OfflineBanner — if it's showing, you're offline. Logs are queued. Get back online.
  2. Look at SyncStatus in the corner — does it say "X waiting to sync"? They'll flush automatically.
  3. Connect to Wi-Fi or cellular. SyncStatus turns green when complete.
  4. Force-quit-then-reopen — re-launches the SyncManager.
  5. Don't clear browser data while offline — wipes IndexedDB and pending writes are lost.

If you're online and writes still fail:

  1. Check DevTools → Network for failed requests (red).
  2. Common: 401 Unauthorized — your session expired. Log out and back in.
  3. Common: 402 TRIAL_EXPIRED — your trainer's tier is locked. Message them.
  4. File a bug if errors persist.

Worst case: export your set log manually (write down what you did) and re-enter via the manual log at /workouts/log after the issue resolves.

Floating WhatsApp button doesn't appear

Trainer hasn't set WhatsApp, or the API call is failing.

Most common cause: your trainer hasn't configured WhatsApp on their profile. Ask them.

Other causes:

  1. You have no active trainer relationship. Open /dashboard/client — does it say "no trainer assigned"? If so, your trainer archived you or invite was never accepted.
  2. API failure. DevTools → Network → look for /api/clients/trainer — if 401 or 500, file a bug.
  3. Component conditionally hidden. The button only renders for role === 'client'. If you're a trainer or admin testing, you won't see it.
  4. Adblock-style extensions. Some block floating chat widgets. Disable extensions and reload.
  5. Fresh install of PWA. First launch may not have hydrated the trainer relationship yet. Reload.

Trainer-side fix:

  • /profile/edit → WhatsApp Setup → enter number with country code OR community link.
  • Save.
  • Client refreshes — button appears.

Quick test: ask trainer to send themselves a WhatsApp link via wa.me and confirm it works in their browser.

Program assignment fails with an error

Empty cycle slots, archived client, or duplicate active assignment.

Common causes:

  1. Cycle has no workout slots. As of 2026-04-27, the API enforces "at least 1 non-rest cycle slot". Open /programs/[id]/edit, drag at least one workout template into the 7-day cycle, save.
  1. Client is archived. Bulk-assign modal only lists active clients. Reactivate via Bulk Operations.
  1. Description too long. 10000-char cap on description. Trim.
  1. Duplicate active assignment. A client already has this program assigned and active. Either archive the old assignment first or pick a different program. (Note: the 500-on-duplicate bug was fixed in the 2026-04 sweep — error should now be friendly.)
  1. 403 — tier limit. Free or Starter trainer trying to assign too many programs at once. Upgrade or assign individually.

Step-by-step diagnosis:

  1. Hover the Assign button — does it open the modal?
  2. Pick the client — do they appear in the dropdown?
  3. Pick start date — try the next Monday.
  4. Click Assign.
  5. Open DevTools → Network → look for POST /api/programs/[id]/assign. Read the response body.

Most response bodies tell you exactly what failed. If the response is a 500 with no body, file a bug.

Analytics charts are empty

No measurements, no completed workouts, or selected wrong client.

Reasons charts can be empty:

  1. No measurements logged. Body composition charts need at least 2 data points to plot a line. Log a measurement at /analytics → Add Measurement.
  1. No completed workouts in date range. Volume / training load charts need completed sessions. Either complete a workout or widen the date range.
  1. Wrong client selected. Trainer view: confirm the client selector at top is set to the right person. If it's blank, you're seeing roster aggregates, not individual.
  1. Tier-locked. Starter trainers see AnalyticsLockedView instead of the dashboard. Upgrade.
  1. Old measurement-attribution bug. Pre-2026-04-20, trainer-recorded measurements were saved against the trainer ID, not the client. Fixed. If you have ancient data, ask support to re-attribute.

Quick fix:

  1. Go to /analytics → History tab.
  2. If the table has rows but charts are empty → date range issue. Set to "All time".
  3. If the table is empty → no data exists. Log some.
  4. If you see other-user data → client selector issue. Pick correct client.

ACWR specifically: ACWR needs at least 4 weeks of data to compute reliably. Charts show "Insufficient data" until then.

PDF report has empty charts / pages

Date range covers no data, or sections were ticked that have no underlying records.

Diagnose:

  1. Date range too narrow. A report for the last 7 days will be empty if no workouts were completed in that window. Pick "Last 90 days" or "All time".
  1. Section ticked but no data exists. E.g. you ticked "Goals" but the client has no goals. Untick sections you know are empty.
  1. Trainer-recorded measurements bug (pre-2026-04-20). Measurements logged before the 2026-04-20 fix may be attributed to you, not the client. Visit /analytics History tab to confirm. Email support if old data needs re-attribution.
  1. PDF sections render but charts blank. Means the data range query returned 0 rows. Either widen the range or seed data.

Quick test:

  • Untick all sections except Cover and Workout Summary.
  • Generate. If THIS is empty, the client has no data in the selected range.
  • Add sections back one at a time.

About the renderer: We rewrote the PDF generator from Puppeteer to jsPDF on 2026-04-20 because Puppeteer was failing on Vercel. jsPDF is pure JS and works reliably. If you remember an old "PDF won't generate" error, that was Puppeteer — gone now.

App feels slow / requests time out

Cold-start, Redis latency, or your network.

Three real causes seen in prod:

1. Neon free-tier cold-start. Our database auto-suspends after idle. First request after a long pause takes 5-10 seconds while Neon spins up. Subsequent requests are fast. This is normal — accepted infra behavior.

2. Upstash Redis degraded (FIXED 2026-04-23). Pre-fix, every authenticated request stalled ~4500ms because tokenService.isTokenBlacklisted() had no timeout. Fixed with 300ms per-op timeout + circuit breaker. If you see > 1s response times consistently, file a bug — Redis may have rotated again.

3. Your network. Open DevTools → Network → check ping/RTT. If you're on hotel Wi-Fi or cellular with low signal, every request crawls. Switch to a better connection.

Diagnose:

  1. Open DevTools → Network.
  2. Reload a page that feels slow.
  3. Sort by Time descending.
  4. Look at the slowest 3 requests — are they all /api/* or all _next/*?
  • All /api/* → backend issue (file bug if persistent).
  • All _next/* → client/CDN issue (clear cache or try different network).

Acceptable latencies:

  • API requests: < 500ms typical, < 2s acceptable.
  • First page paint: < 2s typical.
  • > 5s persistently: file a bug.

Push notifications never arrive

Permission, OS settings, Focus mode, or subscription expired.

Check in order:

  1. App-level toggle on. /profile/edit → Notification Settings → Enable Push Notifications must be ON.
  1. Browser permission granted. Click the lock icon in your address bar → site settings → Notifications: Allow.
  1. OS-level permission granted.
  • iOS: Settings → EvoFit Trainer → Notifications → Allow Notifications.
  • Android: Settings → Apps → EvoFit Trainer → Notifications.
  • macOS: System Settings → Notifications → EvoFit Trainer → Allow.
  1. Focus / Do Not Disturb is OFF. Both Apple and Android Focus modes silently suppress notifications.
  1. PWA is installed (recommended). Push to a browser tab works less reliably than push to an installed PWA. Install via Safari or Chrome.
  1. Subscription not expired. Push subscriptions in Upstash Redis have a 30-day TTL. After 30 days idle, the subscription is dropped. Re-enable in /profile/edit → Notification Settings.
  1. Test the subscription endpoint. Open DevTools → Application → Service Workers — confirm one is registered. Then Application → Notifications → check Permission state is granted.

Trigger a test push:

  • File an in-app bug saying "test push" — admin can manually trigger one.

If absolutely nothing works:

  • Disable notifications in /profile/edit.
  • Hard-refresh.
  • Re-enable.
  • Re-grant browser permission.

This rebuilds the subscription from scratch.

Avatar upload fails or shows an error

HEIC, file too big, animated GIF, or browser permissions — common causes and the fix for each.

The avatar widget at /profile/edit validates files before sending them to the server. Most failures fall into one of five buckets.

Why this happens

The widget runs three checks before saving:

  1. File type must be JPEG, PNG, or WebP.
  2. File size must be under 5 MB raw.
  3. After client-side resize to 256×256 pixels, the data URL must fit under 200 KB.

If any check fails, the widget shows a friendly error message and does not save.

Fix by symptom

"iPhone HEIC photos aren't supported yet"

iPhones default to HEIC format. The widget rejects HEIC because browsers can't reliably decode it for resizing.

Fix:

  1. Open the photo in the iOS Photos app.
  2. Tap Edit in the top right.
  3. Tap Done without making changes — iOS often re-saves as JPEG.
  4. If that doesn't work, share the photo to Mail (to yourself), download from email, then upload that.

Permanent fix: Settings → Camera → Formats → Most Compatible. Future photos save as JPEG by default.

"Animated GIFs aren't supported"

The widget needs a single static frame. Animated GIFs are rejected because the avatar surfaces don't loop animations.

Fix: Convert to JPEG or PNG. Most online tools do this in one click ("save first frame as PNG").

"Image is too large (X MB). Maximum file size is 5.0 MB"

The raw upload is over 5 MB.

Fix:

  • Most phone cameras produce 2–4 MB JPEGs — yours might be set to a larger format.
  • Use a free image compressor or your phone's built-in Edit tool to crop and re-export.
  • Lower the camera resolution in settings.

"Unsupported file type. Please upload a JPEG, PNG, or WebP image"

The file extension isn't one we accept.

Fix: Re-export the file as .jpg, .png, or .webp. Avoid .bmp, .tiff, .svg, .heic, .heif, .gif.

Upload spins forever and never finishes

This is rare and usually a network or browser issue.

Fix in order:

  1. Hard-refresh the page (Cmd/Ctrl + Shift + R).
  2. Try a different browser (Chrome, Safari, Firefox).
  3. Try from a different network (mobile hotspot vs. Wi-Fi).
  4. Check the browser console for errors — if you see a 401 or 403, log out and back in.

What you'll see when it works

  • Circle dims briefly.
  • Status flips through Reading…, Resizing…, Saving….
  • Circle shows the new photo.
  • Button label changes to Change Photo.
  • A green status line confirms "Photo updated".

If this didn't work

If the upload still fails after the fixes above, click Report Bug in the help menu. Include:

  • File type (e.g., .jpg).
  • Original file size.
  • Browser + device (e.g., "Safari on iPhone 13, iOS 18").
  • The exact error message you see.

Where to find this

  • Page: /profile/edit → Profile Photo section.
  • Component: AvatarUpload.
  • Validation logic: lib/avatar.ts.

Related