Overview & Purpose
When users encounter errors, they deserve clear feedback explaining what happened and how to fix it. Raw backend error messages must be normalized to:
- Remove noise: Strip redundant "Error:" prefixes and clean whitespace
- Prevent leakage: Ensure sensitive implementation details never reach the user
- Provide fallbacks: Display safe messages when errors are empty or malformed
- Ensure consistency: Apply the same logic across all 11+ frontend modules
- Improve debugging: Log full error details to Phantom Wing while showing safe summaries to users
The Problem: Generic vs. Meaningful Errors
Before standardization, PayCal modules used ad-hoc error handling:
// ❌ BAD: Exposes raw error, duplicates logic
PC.showToast(error?.message || 'Import failed.');
PW.error(`Import failed: ${error.message}`);
Problems with this approach:
- Users see confusing raw messages like "ECONNREFUSED: Connection refused"
- Each module implements its own fallback logic independently
- No consistent whitespace trimming or prefix stripping
- Empty error messages can display as "undefined" in the UI
The Solution: Standardized Error Resolver
All PayCal frontend modules now use a unified resolver function that normalizes error messages:
// ✅ GOOD: Normalized, consistent, safe
const resolveThrownMessage = (error, fallbackMessage) => {
const raw = typeof error?.message === 'string'
? error.message
: String(error || '');
const normalized = raw.replace(/^Error:\s*/i, '').trim();
return normalized !== '' ? normalized : fallbackMessage;
};
Usage:
// In catch blocks across modules
try {
await updateProfile(data);
} catch (error) {
const message = resolveThrownMessage(error, 'Unable to update profile.');
PC.showToast(message, 'error'); // User sees meaningful feedback
PW.error(message); // Logged for debugging
}
Implementation Scope
As of April 2026, this standardized error-handling pattern has been applied to 11 frontend modules with approximately 40+ normalized catch blocks:
Authentication & Settings (7 modules)
html/js/auth-recovery/index.php(4 catches)html/js/signin/index.php(2 catches)html/js/signin/verification-reminder.js(2 catches)html/js/signin/verification-status-banner.js(1 catch)html/js/settings/index.php(8+ catches)
Core & Data Modules (4 modules)
html/js/core/network.js(3 catches)html/js/core/index.php(5 catches)html/js/core/billing.js(5 catches)html/js/earnings/index.php(4 catches)
High-value modules (10+ catch points):
html/js/organizations/index.php- Org management, access requests, audit trails (19+ catches)html/js/sites/index.php- Site CRUD, earnings, orphan work recovery (10+ catches)html/js/calendar/calendar.js- Day-entry operations, copy/paste/delete (2 catches)
Error Categories & Handling Patterns
The resolver is applied consistently across several error categories:
1. Network Request Failures
// Network module: HTTP errors, timeouts, connection issues
async function deleteResource(ep, id) {
try {
// ...fetch logic...
} catch (error) {
const resolved = resolveThrownMessage(error, 'Network error');
const msg = `[deleteResource] ${resolved}`;
PW.error(msg);
throw new Error(msg);
}
}
2. API Response Handling
// Billing/Settings: Server returned error message in payload
try {
const response = await fetch('/api/v1/billing/subscription');
const payload = await response.json();
if (!response.ok) {
throw new Error(payload?.message || 'Unable to load billing status.');
}
} catch (error) {
const resolved = resolveThrownMessage(error, 'Unable to load billing status.');
setScreenReaderStatus(resolved);
}
3. UI Operation Failures
// Calendar/Organizations: User-initiated actions (paste, delete, update)
button.addEventListener('click', async () => {
try {
await performAction();
PC.showToast('Success!', 'save');
} catch (error) {
const message = resolveThrownMessage(error, 'Action failed. Try again.');
PC.showToast(message, 'error');
}
});
4. Async Initialization
// Core modules: Startup or dependent initialization failures
try {
NavigationToggle.init();
} catch (err) {
const resolved = resolveThrownMessage(err, 'Navigation init failed');
PW.warn(resolved); // Logged but doesn't block page
}
Security Considerations
Error message normalization protects user privacy and system integrity:
- No Database Details: Backend errors like "UNIQUE constraint failed on email" are intercepted at the API boundary and replaced with user-friendly messages
- No File Paths: System errors exposing file paths or process details are stripped
- No Auth Leakage: Responses to authentication failures never reveal whether an account exists (timing-safe generic messages only)
- No CORS/Network Details: Transport-layer errors are normalized to generic "Connection error" messages
- Secure Fallbacks: All catchers have explicit fallback messages; never displays "undefined" or "null"
User Experience Benefits
This standardization gives users clearer, safer, and more consistent feedback while making support and debugging easier for the team.
- Messages are understandable without exposing implementation detail
- Fallback text remains safe when the backend error is missing or malformed
- Logging stays useful for debugging without leaking sensitive values to the UI
Debugging & Support Workflow
Support staff can rely on a consistent resolver path, then inspect Phantom Wing logs for the full technical detail when needed.
- User sees normalized message
- Phantom Wing records the full error context privately
- Support can correlate issue class without exposing the raw backend response
Testing & Quality Assurance
The normalization logic is covered by focused module tests and by integration checks across the main frontend flows.
Maintenance & Future Extensions
As PayCal adds more modules, the same resolver pattern should be reused instead of duplicating custom error formatting.
- New modules should call the shared resolver directly
- Module-specific fallback messages should stay concise and user-facing
- Any changes to the resolver should be regression-tested across representative flows
That keeps behavior predictable as the codebase grows and avoids a second generation of module-specific error handling.
Summary: The PayCal Error-Handling Standard
PayCal standardized frontend error handling so that users get safe, consistent messages while developers still get the information they need for debugging.
- Normalize error messages before display
- Hide implementation details from users
- Use explicit fallback text for empty or malformed errors
- Log technical detail privately for support and debugging