Skip to content

feat(error): implement comprehensive error handling and logging system#52

Merged
jbdevprimary merged 3 commits into
mainfrom
feat/issue-16-error-handling
Jan 18, 2026
Merged

feat(error): implement comprehensive error handling and logging system#52
jbdevprimary merged 3 commits into
mainfrom
feat/issue-16-error-handling

Conversation

@jbdevprimary
Copy link
Copy Markdown
Contributor

@jbdevprimary jbdevprimary commented Jan 18, 2026

Summary

This PR implements a comprehensive error handling and logging system for ThumbCode, addressing Issue #16 (CRITICAL priority).

Key Features

Logging Service (src/lib/logger.ts)

  • Production-grade logger with log levels: debug, info, warn, error, fatal
  • Context enrichment for structured logging
  • Log buffering with configurable size
  • Optional remote logging support
  • Child logger support for component-specific logging
  • Console output with color coding

Error Boundary Components (src/components/error/)

  • ErrorBoundary - React error boundary that catches errors in child components
  • ErrorFallback - Full-screen error display with organic design styling
  • CompactErrorFallback - Inline error display for smaller UI sections
  • withErrorBoundary HOC for easy wrapping of components
  • Reset functionality via resetKeys prop
  • Dev-only debug information display

Global Error Handler (src/lib/error-handler.ts)

  • Centralized AppError type with standardized structure
  • Error codes for categorization (network, API, auth, storage, git, agent errors)
  • User-friendly error messages mapped to error codes
  • parseError() for converting any error to AppError
  • handleError() for consistent error processing
  • Global error handler setup for React Native
  • Error callback registration for app-wide error handling

Retry Utility (src/lib/retry.ts)

  • Exponential backoff with configurable multiplier
  • Jitter support for distributed systems
  • Retry presets (quick, standard, patient, aggressive)
  • withRetry wrapper for creating retryable functions
  • Network and rate limit error detection helpers

Network Error Hook (src/hooks/use-network-error.ts)

  • useNetworkError - Full network state monitoring with error handling
  • useIsOnline - Simple connectivity check hook
  • Automatic offline detection
  • Integration with error handling system

Integration

  • ErrorBoundary wraps the root layout
  • Global error handlers initialized on app start
  • Logger info message on app startup

Test Plan

  • Verify app starts without errors
  • Test error boundary by triggering a component error
  • Verify error fallback UI displays correctly
  • Test retry functionality with network requests
  • Verify logging output in console
  • Test offline detection behavior

Closes #16

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • User-friendly error screens with retry and report actions; app-wide error recovery handling.
    • Network connectivity detection with conditional retry behavior for offline scenarios.
    • Automatic onboarding navigation that redirects based on completion status.
  • Chores

    • Updated dependencies for network monitoring and navigation libraries.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add production-grade logging service with levels (debug, info, warn, error, fatal)
- Add Logger class with context enrichment and optional remote logging
- Create ErrorBoundary component for catching React errors
- Create ErrorFallback component with organic design styling
- Add CompactErrorFallback for inline error display
- Implement global error handler with standardized AppError type
- Add error codes and user-friendly error messages
- Create retry utility with exponential backoff and jitter
- Add retry presets (quick, standard, patient, aggressive)
- Implement useNetworkError hook for network status monitoring
- Implement useIsOnline hook for simple connectivity checks
- Integrate ErrorBoundary in root layout
- Initialize global error handlers on app start

Closes #16

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 18, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Initializes global error handling and logging, adds React ErrorBoundary and fallback UI, implements a retry/backoff utility and network-aware hooks, exposes lib exports, and integrates error/logger setup plus onboarding redirect logic into the root layout. New tests cover logger, retry, and error-handler modules.

Changes

Cohort / File(s) Summary
Root layout & deps
app/_layout.tsx, package.json
Initializes global error handlers and logger at module load, wraps root layout with ErrorBoundary, and adds router-based onboarding redirect logic. Updates package.json to add/deduplicate dependencies including NetInfo and React Navigation.
Error UI & boundary
src/components/error/ErrorBoundary.tsx, src/components/error/ErrorFallback.tsx, src/components/error/index.ts
Adds ErrorBoundary class component with lifecycle handling, reset logic, and withErrorBoundary HOC. Adds ErrorFallback and CompactErrorFallback UI components and re-exports them from the error index.
Global error handling
src/lib/error-handler.ts, src/lib/index.ts
Introduces AppError model, error codes/severities, parse/create/normalize utilities, handleError, onError callbacks, and setupGlobalErrorHandlers. Re-exports error APIs from src/lib/index.ts.
Logging service
src/lib/logger.ts, src/lib/__tests__/logger.test.ts
Adds a production-ready Logger and ChildLogger with levels, context enrichment, buffering, console/remote output, and tests validating behavior and buffering. Exposes singleton logger.
Retry utilities
src/lib/retry.ts, src/lib/__tests__/retry.test.ts
Adds retry with exponential backoff + jitter, withRetry, retry presets, and helpers isNetworkError/isRateLimitError/networkRetryable. Includes tests for backoff, callbacks, and presets.
Network hooks & exports
src/hooks/use-network-error.ts, src/hooks/index.ts
Adds useNetworkError hook (NetInfo subscription, error state, retryIfOnline) and useIsOnline. Consolidates hooks exports to expose network-related hooks and types.
Tests for error handler
src/lib/__tests__/error-handler.test.ts
Adds comprehensive tests for error-handler covering creation, parsing, detection, user messages, handleError, and onError registration/unregistration.

Sequence Diagram(s)

sequenceDiagram
    participant Component as React Component
    participant ErrorBoundary as ErrorBoundary
    participant ErrorHandler as Error Handler
    participant Logger as Logger Service
    participant Fallback as ErrorFallback UI

    Component->>Component: render / lifecycle
    Component--xComponent: throws error
    ErrorBoundary->>ErrorBoundary: getDerivedStateFromError()
    ErrorBoundary->>ErrorHandler: handleError(error, { component: ... })
    ErrorHandler->>Logger: logger.error/fatal(...)
    Logger->>Logger: buffer log entry
    ErrorBoundary->>ErrorBoundary: componentDidCatch()
    ErrorBoundary->>Fallback: render fallback UI
    Fallback-->>User: show message + retry
    User->>Fallback: presses retry
    Fallback->>ErrorBoundary: invoke onRetry -> resetErrorBoundary()
    ErrorBoundary->>Component: re-render children
Loading
sequenceDiagram
    participant App as Application
    participant Hook as useNetworkError
    participant NetInfo as NetInfo Service
    participant ErrorHandler as Error Handler
    participant RetryFn as Async Operation

    App->>Hook: mount hook
    Hook->>NetInfo: subscribe()
    NetInfo-->>Hook: state update (online/offline)
    Hook->>Hook: update network state & log via logger
    App->>Hook: retryIfOnline(asyncFn)
    Hook->>Hook: isOnline?
    alt Online
        Hook->>RetryFn: execute asyncFn (with retry util as needed)
        RetryFn-->>Hook: result / error
        Hook->>Hook: clear/set error state
        Hook-->>App: return result
    else Offline
        Hook->>ErrorHandler: handleError(NETWORK_OFFLINE)
        ErrorHandler->>Logger: logger.warn(...)
        Hook-->>App: return null
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I scoped the stack and logged each hop,
Boundaries placed so crashes stop.
When networks fail I retry with grace,
A rabbit’s patch keeps pace and place.
Errors caught — the app can hop on top!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements most core requirements: error boundaries [#16], global error handler [#16], logging service [#16], retry mechanisms [#16], network error handling [#16]. Notable gap: error reporting integration (Sentry/Bugsnag) not addressed, and tests not included in changeset. Integrate error reporting service (Sentry/Bugsnag) and add test coverage for error boundaries, handler, logging, and retry logic to fully satisfy issue requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(error): implement comprehensive error handling and logging system' accurately summarizes the main change—a comprehensive error handling and logging system for the application.
Out of Scope Changes check ✅ Passed The PR modifies package.json, app/_layout.tsx, hooks/index.ts alongside error handling additions. These are directly related to integrating error handling into the app's initialization and exports.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jbdevprimary
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- Add tests for Logger class (log levels, filtering, context, buffering)
- Add tests for error handler (createAppError, parseError, handleError)
- Add tests for retry utility (retry, withRetry, presets, error detection)
- Increases test coverage from ~5% to ~34%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 35-38: Update the dependency version for
`@react-native-async-storage/async-storage` in package.json from "^2.0.0" to a
newer 2.x patch (e.g., "^2.2.0" or later) to avoid Kotlin/KSP Android build
issues; edit the dependency entry for
"@react-native-async-storage/async-storage" in package.json and run your package
manager's install command to lock the updated version.

In `@src/components/error/ErrorFallback.tsx`:
- Around line 103-107: The "Report Issue" Pressable in the ErrorFallback
component is missing an onPress handler so the button is non-functional; add an
onPress prop to the Pressable (or create a handler method like handleReportIssue
inside ErrorFallback) that either triggers a placeholder action (e.g., show an
alert/toast saying "Coming soon") or opens the real reporting flow, and include
a TODO comment if it's a temporary stub; ensure the handler is properly
bound/passed to the Pressable's onPress prop so the button becomes interactive.

In `@src/hooks/use-network-error.ts`:
- Around line 40-67: The effect's event listener closure wrongly reads a stale
network state (it captures the `network` value when the effect was created),
causing incorrect "Device came back online" detection; change to track previous
connection state with a ref (e.g., create a React ref prevIsConnectedRef and
update it whenever you setNetwork) and inside the NetInfo.addEventListener
callback compare state.isConnected with prevIsConnectedRef.current (instead of
`network.isConnected`), then after handling logging update
prevIsConnectedRef.current to state.isConnected so `useEffect` no longer relies
on the `network` closure for correct online/offline detection in the `useEffect`
+ `NetInfo.addEventListener` listener.

In `@src/lib/retry.ts`:
- Around line 72-119: The retry function can be called with maxAttempts = 0
which makes the for-loop skip, leaves lastError undefined, and causes throw
undefined; fix this by validating or normalizing maxAttempts at start of retry
(or DEFAULT_OPTIONS merge) so it is at least 1, and if the provided value is
invalid throw a clear Error; update the retry function (refer to function name
retry, local variable maxAttempts, and lastError) to either set maxAttempts =
Math.max(1, maxAttempts) or immediately throw a descriptive Error when
maxAttempts < 1, and ensure the final thrown value is a proper Error (not
undefined) and onFailure/onRetry callbacks receive a meaningful error object.
🧹 Nitpick comments (8)
src/components/error/ErrorBoundary.tsx (2)

52-62: Reset key comparison may miss array length changes.

The current comparison only checks if values at existing indices changed. If resetKeys array length changes (e.g., [a, b][a, b, c]), the reset won't trigger since indices 0 and 1 still match.

♻️ Suggested fix to also detect length changes
   componentDidUpdate(prevProps: Props): void {
     // Reset error state when resetKeys change
     if (this.state.hasError && this.props.resetKeys) {
+      const prevKeys = prevProps.resetKeys ?? [];
+      const currKeys = this.props.resetKeys;
+      
+      const hasResetKeyChanged =
+        prevKeys.length !== currKeys.length ||
+        currKeys.some((key, index) => key !== prevKeys[index]);
-      const hasResetKeyChanged = this.props.resetKeys.some(
-        (key, index) => key !== prevProps.resetKeys?.[index]
-      );

       if (hasResetKeyChanged) {
         this.resetErrorBoundary();
       }
     }
   }

97-108: Consider adding displayName for better debugging.

The HOC returns a generic function name which makes debugging harder in React DevTools. Adding a displayName improves the developer experience.

♻️ Suggested improvement
 export function withErrorBoundary<P extends object>(
   WrappedComponent: React.ComponentType<P>,
   fallback?: ReactNode
 ) {
-  return function WithErrorBoundary(props: P) {
+  function WithErrorBoundary(props: P) {
     return (
       <ErrorBoundary fallback={fallback}>
         <WrappedComponent {...props} />
       </ErrorBoundary>
     );
-  };
+  }
+  
+  WithErrorBoundary.displayName = `withErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
+  return WithErrorBoundary;
 }
src/lib/retry.ts (1)

186-196: Rate limit detection relies on message content.

The isRateLimitError checks for "429" in the error message, but HTTP errors may carry the status code in a separate property (e.g., error.status or error.statusCode) rather than the message. Consider enhancing detection for common HTTP error shapes.

♻️ Optional enhancement
 export function isRateLimitError(error: unknown): boolean {
   if (error instanceof Error) {
     const message = error.message.toLowerCase();
-    return (
+    const messageMatch =
       message.includes('rate limit') ||
       message.includes('too many requests') ||
-      message.includes('429')
-    );
+      message.includes('429');
+    
+    // Check for status code property common in HTTP errors
+    const status = (error as { status?: number; statusCode?: number }).status ??
+                   (error as { status?: number; statusCode?: number }).statusCode;
+    
+    return messageMatch || status === 429;
   }
   return false;
 }
app/_layout.tsx (1)

19-21: Module-level side effects may complicate testing and initialization order.

setupGlobalErrorHandlers() and logger.info() are invoked during module evaluation. This can cause issues with:

  • Testing: side effects run on import
  • Initialization order: dependencies may not be ready
  • Multiple imports: though bundlers usually dedupe, edge cases exist

Consider moving these calls inside a useEffect in RootLayout or using a dedicated initialization pattern.

♻️ Suggested refactor
-// Initialize global error handlers
-setupGlobalErrorHandlers();
-logger.info('ThumbCode app started');
-
 function RootLayoutNav() {
   // ... existing code
 }

 export default function RootLayout() {
+  useEffect(() => {
+    setupGlobalErrorHandlers();
+    logger.info('ThumbCode app started');
+  }, []);
+
   return (
     <ErrorBoundary>
src/lib/error-handler.ts (3)

180-182: Type guard may produce false positives.

The check 'code' in error && 'severity' in error will match any Error that happens to have code and severity properties, even if they weren't created by createAppError. Consider also checking for error.name === 'AppError':

♻️ Proposed stricter type guard
 export function isAppError(error: unknown): error is AppError {
-  return error instanceof Error && 'code' in error && 'severity' in error;
+  return (
+    error instanceof Error &&
+    error.name === 'AppError' &&
+    'code' in error &&
+    'severity' in error
+  );
 }

134-151: String-based error detection is fragile but acceptable.

Error message matching ('Network request failed', 'timeout') may miss errors with different message formats across platforms or libraries. This is a common pattern and acceptable for initial implementation, but consider documenting the limitation or adding additional detection patterns over time.


240-251: Minor: Comment is inaccurate.

The comment says "Handle unhandled promise rejections" but React Native's ErrorUtils handles all uncaught JavaScript errors, not just promise rejections. Promise rejections are typically handled separately via global.HermesInternal?.enablePromiseRejectionTracker or similar.

📝 Proposed comment fix
   if (errorUtils) {
-    // Handle unhandled promise rejections
+    // Handle uncaught JavaScript errors
     const originalHandler = errorUtils.getGlobalHandler?.();
src/lib/logger.ts (1)

212-226: Consider adding a timeout for remote logging requests.

The fetch call has no timeout, which could cause hanging requests if the remote endpoint is unresponsive. While failures are silently ignored (appropriate for logging), hanging requests accumulate resources.

♻️ Proposed fix with AbortController timeout
   private async sendToRemote(entry: LogEntry): Promise<void> {
     if (!this.config.remoteEndpoint) return;

     const payload = {
       ...entry,
       appVersion: this.config.appVersion,
       environment: this.config.environment,
     };

+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 5000);
+
-    await fetch(this.config.remoteEndpoint, {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify(payload),
-    });
+    try {
+      await fetch(this.config.remoteEndpoint, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(payload),
+        signal: controller.signal,
+      });
+    } finally {
+      clearTimeout(timeoutId);
+    }
   }

Comment thread package.json
Comment on lines +35 to +38
"@anthropic-ai/sdk": "^0.32.0",
"@react-native-async-storage/async-storage": "^2.0.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@react-native-community/netinfo 11.4.1 React Native 0.76.0 compatibility requirements

💡 Result:

Short answer: Yes — @react-native-community/netinfo 11.4.1 is compatible with React Native 0.76. The package supports React Native >= 0.60 (autolinking) and lists react-native as a peer dependency; RN 0.76 meets that requirement. [1][2]

Sources:

  • package README / npm (React Native compatibility: supports RN 0.60+ with autolinking). [1]
  • package metadata for v11.4.1 (peerDependency react-native; v11.4.1 published). [2]

References:
[1] npm package README for @react-native-community/netinfo.
[2] jsDocs / npm metadata for @react-native-community/netinfo v11.4.1.


🌐 Web query:

@react-native-async-storage/async-storage 2.0.0 React Native 0.76.0 compatibility

💡 Result:

Short answer: Generally yes — @react-native-async-storage/async-storage v2.x supports React Native >=0.60 (so it should work with RN 0.76.0). [1][2]

Notes and cautions:

  • The official docs state it requires RN 0.60+ (autolinking etc.). [1]
  • There are reported Android build issues for version 2.0.0 related to Kotlin/KSP/Kotlin-2 toolchain (you may hit "ksp is too old for kotlin-2" errors). If you use Async Storage 2.0.0 on RN 0.76.0 and your Android toolchain uses Kotlin 2.x, you may need to either upgrade the library to a later patch that fixes Kotlin compatibility or align/downgrade your Kotlin/KSP versions per the issue discussion. [2]
  • The npm package currently has newer 2.x releases (e.g., 2.2.0); prefer the latest 2.x patch release to avoid known 2.0.0 bugs. [3]

Recommendations:

  • Use the latest @react-native-async-storage/async-storage 2.x (not exactly 2.0.0) if possible. [3]
  • If you must stay on 2.0.0 and see Kotlin/KSP errors, align your Android Kotlin/KSP plugin to the versions compatible with the library (or downgrade Kotlin to 1.9.x) as discussed in the GitHub issue. [2]

Sources:
[1] Async Storage install docs (requires RN 0.60+).
[2] GitHub issue reporting Kotlin/KSP compatibility with version 2.0.0.
[3] npm package page showing newer 2.x releases (e.g., 2.2.0).


Upgrade @react-native-async-storage/async-storage to the latest 2.x patch version (≥2.2.0).

While @react-native-community/netinfo@^11.4.1 is compatible with React Native 0.76.0, @react-native-async-storage/async-storage@2.0.0 has documented Android build issues with Kotlin 2.x/KSP toolchains (reported as "ksp is too old for kotlin-2" errors). Upgrade to a newer 2.x patch release (e.g., 2.2.0 or later) to avoid potential Android build failures.

🤖 Prompt for AI Agents
In `@package.json` around lines 35 - 38, Update the dependency version for
`@react-native-async-storage/async-storage` in package.json from "^2.0.0" to a
newer 2.x patch (e.g., "^2.2.0" or later) to avoid Kotlin/KSP Android build
issues; edit the dependency entry for
"@react-native-async-storage/async-storage" in package.json and run your package
manager's install command to lock the updated version.

Comment thread src/components/error/ErrorFallback.tsx Outdated
Comment thread src/hooks/use-network-error.ts Outdated
Comment thread src/lib/retry.ts
- Add onPress handler to Report Issue button in ErrorFallback
- Add onReportIssue prop for custom report handling
- Fix stale closure in useNetworkError by using ref for previous state
- Validate maxAttempts in retry function (normalize to at least 1)
- Initialize lastError to avoid throwing undefined
- Remove unused type import in error-handler tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
58.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@jbdevprimary jbdevprimary merged commit 1c5d35a into main Jan 18, 2026
10 of 12 checks passed
@jbdevprimary jbdevprimary deleted the feat/issue-16-error-handling branch January 18, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Production] Implement comprehensive error handling and logging

1 participant