Improve error messages for network and API failures - #305
Conversation
Add a centralized AppErrorHandler that maps network, timeout, auth, and server failures to consistent copy, and wire it through Supabase/ AI service error returns and UI SnackBars. Co-authored-by: Cursor <cursoragent@cursor.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
WalkthroughThe PR adds centralized error classification, user-facing messages, and snackbar presentation. Authentication flows, backend services, AI requests, and application screens now use mapped errors instead of exposing raw exceptions or backend error strings. ChangesCentralized error handling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Screen
participant SupabaseService
participant AppErrorHandler
participant SnackBar
Screen->>SupabaseService: Execute operation
SupabaseService-->>Screen: Error result or exception
Screen->>AppErrorHandler: showSnackBar(context, error)
AppErrorHandler->>SnackBar: Display mapped message
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Honor HTTP status codes in AppErrorHandler.messageFor before treating string errors as domain messages so 5xx responses surface server copy. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
lib/screens/tickets/ticket_detail_screen.dart (1)
197-229: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winRoute non-throwing status and priority errors through the mapper.
These branches still interpolate
result['error']directly into the SnackBar. That bypasses the centralized sanitization and can expose backend-specific text or inconsistent messages. UseAppErrorHandler.messageFor(result['error'], fallback: ...)for both operations.Also applies to: 243-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/tickets/ticket_detail_screen.dart` around lines 197 - 229, Update the failure SnackBar branches in _updateTicketStatus and the corresponding priority-update operation to pass result['error'] through AppErrorHandler.messageFor with an appropriate fallback message, instead of interpolating the raw error directly. Preserve the existing success behavior and use the mapped message for both non-throwing operations.lib/screens/tasks/task_screen.dart (2)
98-116: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGuard error SnackBars after disposal.
Both
catchblocks useScaffoldMessenger.of(context)without checkingmounted. If the screen is popped while the request is pending, the error handler can itself fail. Return early when!mountedbefore accessing the context.As per path instructions, async UI work must not update a disposed widget.
Also applies to: 118-136
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/tasks/task_screen.dart` around lines 98 - 116, Add a !mounted early return at the start of the catch block in _updateTaskStatus before calling ScaffoldMessenger.of(context). Apply the same mounted guard to the other async error catch block covering the referenced range, while preserving existing logging and SnackBar behavior for mounted screens.Source: Path instructions
98-116: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHonor the service result-map contract at every update caller.
The Supabase update methods catch backend failures and return
{success: false, error: ...}rather than throwing. Callers must inspect that result before reloading or showing success.
lib/screens/tasks/task_screen.dart#L98-L116: check the status-update result and surfaceresult['error'].lib/screens/tasks/task_screen.dart#L118-L136: check the approval-update result before reloading.lib/screens/tickets/ticket_screen.dart#L83-L103: check the ticket status-update result.lib/screens/tickets/ticket_screen.dart#L105-L125: check the ticket approval result.lib/screens/tasks/task_detail_screen.dart#L115-L147: only show approval success whenresult['success'] == true.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/tasks/task_screen.dart` around lines 98 - 116, Honor the result-map contract in _updateTaskStatus and the corresponding approval/status update callers: inspect each service result's success value before reloading tasks or tickets, surface result['error'] on failure, and retain exception handling for thrown errors. In lib/screens/tasks/task_screen.dart ranges 98-116 and 118-136, lib/screens/tickets/ticket_screen.dart ranges 83-103 and 105-125, apply these checks before reloads; in lib/screens/tasks/task_detail_screen.dart range 115-147, show approval success only when result['success'] == true.lib/screens/meetings/meeting_insights_screen.dart (1)
45-64: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftPreserve the distinction between failed loads and missing records.
SupabaseService.getMeetingDetailsandSupabaseService.getTicketDetailscatch exceptions and returnnull, so the newly added UI catches cannot map network/server failures. Both screens need a result contract that distinguishes “not found” from “request failed.”
lib/screens/meetings/meeting_insights_screen.dart#L45-L64: show the mapped failure instead of rendering empty insight content.lib/screens/tickets/ticket_detail_screen.dart#L36-L88: show the mapped failure instead of treating the ticket as absent.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/meetings/meeting_insights_screen.dart` around lines 45 - 64, Update SupabaseService.getMeetingDetails and getTicketDetails to preserve separate not-found and request-failure outcomes rather than converting all exceptions to null; then update lib/screens/meetings/meeting_insights_screen.dart lines 45-64 and lib/screens/tickets/ticket_detail_screen.dart lines 36-88 to consume that contract, render empty content only for missing records, and display AppErrorHandler.messageFor(e) for failures at each site.lib/screens/tasks/task_detail_screen.dart (1)
74-113: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the mapped error on non-throwing status failures.
updateTaskStatusreturns{success: false, error: ...}for backend failures, but this branch replaces that message with the generic “Failed to update task status.” ShowAppErrorHandler.messageFor(result['error'], fallback: ...)here so network, authentication, and server guidance is not discarded.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/tasks/task_detail_screen.dart` around lines 74 - 113, The non-throwing failure branch in _updateTaskStatus discards the backend error details. Preserve the updateTaskStatus result, extract its error value, and use AppErrorHandler.messageFor with the existing generic failure text as the fallback when constructing the failure SnackBar.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/screens/auth/login_screen.dart`:
- Around line 98-103: Update the catch block in the login flow to replace
AppErrorHandler.networkMessage with a login-specific fallback such as “Unable to
sign in. Please try again.”, while preserving the existing
AppErrorHandler.showSnackBar call and network-exception classification.
- Around line 135-140: Replace each local ScaffoldMessenger SnackBar in the
mapped-error branches with the shared AppErrorHandler.showSnackBar presenter,
preserving the existing result error mapping and fallback messages. Apply this
in lib/screens/auth/login_screen.dart:135-140,
lib/screens/auth/signup_screen.dart:180-186,
lib/screens/auth/team_selection_dialog.dart:57-63 and 94-100,
lib/screens/profile/edit_profile_screen.dart:106-111, and
lib/screens/profile/profile_screen.dart:65-70, 133-138, 398-403, and 413-418.
In `@lib/screens/home/dashboard_screen.dart`:
- Line 154: Replace each manual error SnackBar with the centralized
AppErrorHandler.showSnackBar presenter, passing context and the original error
value so it handles message mapping, queue clearing, and floating behavior.
Apply this in lib/screens/home/dashboard_screen.dart lines 154 and 169;
lib/screens/meetings/create_meeting_screen.dart lines 211 and 226;
lib/screens/meetings/meeting_detail_screen.dart lines 117, 135, 146, 234,
297-304, and 338-345; lib/screens/meetings/meeting_screen.dart lines 80, 92, and
122; lib/screens/profile/team_members_screen.dart line 52; and
lib/screens/tasks/create_task_screen.dart lines 110 and 124. Use the relevant
result error or caught exception at each site and remove the locally constructed
SnackBar content.
In `@lib/services/ai_service.dart`:
- Around line 431-442: Update the http.post calls in generateChatResponse and
handleToolResponse to apply a bounded timeout of approximately 10–15 seconds,
and explicitly handle timeout exceptions so they reach the existing mapped error
response path.
- Around line 431-442: Update the Gemini API error handling in the
response-status branch of the chat-generation method so 401 and 403 use a
Gemini-specific credential/API-access message instead of
AppErrorHandler.messageFor’s session-expired mapping. Preserve serverMessage for
5xx responses and keep existing handling for other statuses.
In `@lib/utils/app_error_handler.dart`:
- Around line 237-242: Update the _looksUserFacing branch in the app error
handling flow to avoid returning raw PostgrestException messages. Map
PostgrestException instances to safe, predefined user-facing copy, while
preserving raw messages only for explicitly service-owned domain errors.
---
Outside diff comments:
In `@lib/screens/meetings/meeting_insights_screen.dart`:
- Around line 45-64: Update SupabaseService.getMeetingDetails and
getTicketDetails to preserve separate not-found and request-failure outcomes
rather than converting all exceptions to null; then update
lib/screens/meetings/meeting_insights_screen.dart lines 45-64 and
lib/screens/tickets/ticket_detail_screen.dart lines 36-88 to consume that
contract, render empty content only for missing records, and display
AppErrorHandler.messageFor(e) for failures at each site.
In `@lib/screens/tasks/task_detail_screen.dart`:
- Around line 74-113: The non-throwing failure branch in _updateTaskStatus
discards the backend error details. Preserve the updateTaskStatus result,
extract its error value, and use AppErrorHandler.messageFor with the existing
generic failure text as the fallback when constructing the failure SnackBar.
In `@lib/screens/tasks/task_screen.dart`:
- Around line 98-116: Add a !mounted early return at the start of the catch
block in _updateTaskStatus before calling ScaffoldMessenger.of(context). Apply
the same mounted guard to the other async error catch block covering the
referenced range, while preserving existing logging and SnackBar behavior for
mounted screens.
- Around line 98-116: Honor the result-map contract in _updateTaskStatus and the
corresponding approval/status update callers: inspect each service result's
success value before reloading tasks or tickets, surface result['error'] on
failure, and retain exception handling for thrown errors. In
lib/screens/tasks/task_screen.dart ranges 98-116 and 118-136,
lib/screens/tickets/ticket_screen.dart ranges 83-103 and 105-125, apply these
checks before reloads; in lib/screens/tasks/task_detail_screen.dart range
115-147, show approval success only when result['success'] == true.
In `@lib/screens/tickets/ticket_detail_screen.dart`:
- Around line 197-229: Update the failure SnackBar branches in
_updateTicketStatus and the corresponding priority-update operation to pass
result['error'] through AppErrorHandler.messageFor with an appropriate fallback
message, instead of interpolating the raw error directly. Preserve the existing
success behavior and use the mapped message for both non-throwing operations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: baeb74de-a38b-4283-b212-b4c285fc543f
📒 Files selected for processing (25)
lib/screens/auth/forgot_password_screen.dartlib/screens/auth/login_screen.dartlib/screens/auth/set_new_password_screen.dartlib/screens/auth/signup_screen.dartlib/screens/auth/team_selection_dialog.dartlib/screens/auth/verify_otp_screen.dartlib/screens/chat/chat_screen.dartlib/screens/home/dashboard_screen.dartlib/screens/meetings/create_meeting_screen.dartlib/screens/meetings/meeting_detail_screen.dartlib/screens/meetings/meeting_insights_screen.dartlib/screens/meetings/meeting_screen.dartlib/screens/profile/edit_profile_screen.dartlib/screens/profile/profile_screen.dartlib/screens/profile/team_members_screen.dartlib/screens/tasks/create_task_screen.dartlib/screens/tasks/task_detail_screen.dartlib/screens/tasks/task_screen.dartlib/screens/tickets/create_ticket_screen.dartlib/screens/tickets/ticket_detail_screen.dartlib/screens/tickets/ticket_screen.dartlib/services/ai_service.dartlib/services/supabase_service.dartlib/utils/app_error_handler.darttest/app_error_handler_test.dart
Use login-specific fallbacks, route mapped errors through AppErrorHandler.showSnackBar, avoid leaking PostgREST details, add Gemini HTTP timeouts, and keep 401/403 as API-key errors not session expiry. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
lib/screens/home/dashboard_screen.dart (1)
147-152: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winReset the loading state on service failures.
_switchTeamsets_isLoadingtotrue, but the non-exception failure branch only shows the SnackBar. Unlike thecatchpath, it never sets_isLoadingback tofalse, leaving the dashboard stuck on its loading skeleton.Proposed fix
} else { if (mounted) { + setState(() { + _isLoading = false; + }); AppErrorHandler.showSnackBar(context, result['error']); } }As per path instructions, verify proper error handling and exception catching.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/home/dashboard_screen.dart` around lines 147 - 152, Update the non-exception failure branch in _switchTeam to set _isLoading back to false before or alongside showing the error SnackBar, matching the catch path. Preserve the existing success reload behavior and mounted check so service failures do not leave the dashboard stuck loading.Source: Path instructions
lib/screens/tickets/ticket_screen.dart (1)
757-766: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse
showSnackBarfor delete failures too.This branch calls
messageForbut still builds its ownScaffoldMessenger/SnackBar, so delete failures do not receive the centralized helper’s presentation behavior. UseshowSnackBarwith the existing fallback to keep this path consistent with the catch branch.Proposed fix
- if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar( - SnackBar( - content: Text( - AppErrorHandler.messageFor( - result['error'], - fallback: - 'Failed to delete ticket', - ), - ), - backgroundColor: Colors.red, - ), - ); - } + if (context.mounted) { + AppErrorHandler.showSnackBar( + context, + result['error'], + fallback: 'Failed to delete ticket', + ); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/tickets/ticket_screen.dart` around lines 757 - 766, Update the delete-failure branch in the ticket deletion flow to call the centralized showSnackBar helper with AppErrorHandler.messageFor(result['error'], fallback: 'Failed to delete ticket') instead of constructing its own ScaffoldMessenger/SnackBar, matching the existing catch branch behavior.lib/services/ai_service.dart (2)
540-555: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not report Gemini failures as successful tool execution.
The timeout added at Line 540 reaches the catch block, but Lines 549-555 return
"Function executed successfully."for both timeouts and non-200 responses. Return mapped failure text instead.Proposed fix
} else { debugPrint('Error from Gemini API: ${response.statusCode} ${response.body}'); - return 'Function executed successfully.'; + if (response.statusCode == 401 || response.statusCode == 403) { + return 'AI service credentials are invalid or missing. Please check your API key configuration.'; + } + return AppErrorHandler.messageFor( + 'Gemini API error', + statusCode: response.statusCode, + fallback: AppErrorHandler.serverMessage, + ); } } catch (e) { debugPrint('Error handling tool response: $e'); - return 'Function executed successfully.'; + return AppErrorHandler.messageFor( + e, + fallback: AppErrorHandler.serverMessage, + ); }As per path instructions, verify proper error handling and async/await use.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/services/ai_service.dart` around lines 540 - 555, Update the tool-response handling around the Gemini request and its catch block so timeouts, exceptions, and non-200 responses return mapped failure text rather than “Function executed successfully.” Preserve the success response for valid 200 responses with usable candidate text, and ensure the async timeout/error path is properly awaited and handled.Source: Path instructions
446-451: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winInclude initialization in the mapped error path.
Line 63 and Line 64 execute before this
try, so API-key or Supabase initialization failures escapegenerateChatResponseinstead of returning its error result. Move the initialization guard inside thetry.As per path instructions, verify proper error handling and async/await use.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/services/ai_service.dart` around lines 446 - 451, Move the API-key and Supabase initialization guard into the try block of generateChatResponse so initialization failures are caught and returned through the existing mapped error result. Preserve the current AppErrorHandler.messageFor handling and ensure all asynchronous initialization remains properly awaited.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/screens/meetings/meeting_screen.dart`:
- Around line 77-81: Update the deletion completion logic around
_loadInitialData so it returns immediately when the widget is unmounted, before
either branch performs UI work. For mounted widgets, handle unsuccessful results
by showing the error snackbar and successful results by calling
_loadInitialData().
In `@lib/screens/tasks/task_detail_screen.dart`:
- Around line 102-105: Route the false-result failure branch of updateTaskStatus
in lib/screens/tasks/task_detail_screen.dart through
AppErrorHandler.showSnackBar instead of constructing a SnackBar manually; also
update the null ticket-details failure path in
lib/screens/tickets/ticket_detail_screen.dart to use
AppErrorHandler.showSnackBar with a fallback message. Keep exception handling
and successful-result behavior unchanged.
In `@lib/screens/tickets/ticket_detail_screen.dart`:
- Around line 205-208: Update both response-level failure branches in
lib/screens/tickets/ticket_detail_screen.dart at lines 205-208 and 246-249 to
route failures through AppErrorHandler.showSnackBar instead of displaying
result['error'] directly. Ensure status and priority update failures show
sanitized user-facing messages without exposing raw backend details; the catch
branch at the anchor already uses the required handler.
---
Outside diff comments:
In `@lib/screens/home/dashboard_screen.dart`:
- Around line 147-152: Update the non-exception failure branch in _switchTeam to
set _isLoading back to false before or alongside showing the error SnackBar,
matching the catch path. Preserve the existing success reload behavior and
mounted check so service failures do not leave the dashboard stuck loading.
In `@lib/screens/tickets/ticket_screen.dart`:
- Around line 757-766: Update the delete-failure branch in the ticket deletion
flow to call the centralized showSnackBar helper with
AppErrorHandler.messageFor(result['error'], fallback: 'Failed to delete ticket')
instead of constructing its own ScaffoldMessenger/SnackBar, matching the
existing catch branch behavior.
In `@lib/services/ai_service.dart`:
- Around line 540-555: Update the tool-response handling around the Gemini
request and its catch block so timeouts, exceptions, and non-200 responses
return mapped failure text rather than “Function executed successfully.”
Preserve the success response for valid 200 responses with usable candidate
text, and ensure the async timeout/error path is properly awaited and handled.
- Around line 446-451: Move the API-key and Supabase initialization guard into
the try block of generateChatResponse so initialization failures are caught and
returned through the existing mapped error result. Preserve the current
AppErrorHandler.messageFor handling and ensure all asynchronous initialization
remains properly awaited.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5aaa664d-31cb-4dc7-917d-8bd8c66b2e62
📒 Files selected for processing (20)
lib/screens/auth/login_screen.dartlib/screens/auth/signup_screen.dartlib/screens/auth/team_selection_dialog.dartlib/screens/home/dashboard_screen.dartlib/screens/meetings/create_meeting_screen.dartlib/screens/meetings/meeting_detail_screen.dartlib/screens/meetings/meeting_insights_screen.dartlib/screens/meetings/meeting_screen.dartlib/screens/profile/edit_profile_screen.dartlib/screens/profile/profile_screen.dartlib/screens/profile/team_members_screen.dartlib/screens/tasks/create_task_screen.dartlib/screens/tasks/task_detail_screen.dartlib/screens/tasks/task_screen.dartlib/screens/tickets/create_ticket_screen.dartlib/screens/tickets/ticket_detail_screen.dartlib/screens/tickets/ticket_screen.dartlib/services/ai_service.dartlib/utils/app_error_handler.darttest/app_error_handler_test.dart
Guard meeting delete refresh against unmounted widgets, and route task/ticket failure SnackBars through AppErrorHandler so raw backend errors are not shown. Co-authored-by: Cursor <cursoragent@cursor.com>
Closes #303
📝 Description
Adds a centralized frontend error mapper so network, timeout, authentication, and server failures show clear, consistent user-facing messages instead of raw exception dumps.
🔧 Changes Made
lib/utils/app_error_handler.dartto classify failures and map them to the messages from FEATURE REQUEST:Improve Error Messages for Network and API Failures #303 (network / timeout / auth / server)AppErrorHandler.showSnackBarfor consistent floating error SnackBarsAppErrorHandler.messageForinto Supabase service error returns and Gemini chat error content$e/e.toString()test/app_error_handler_test.dart📷 Screenshots or Visual Changes (if applicable)
No layout changes — SnackBars now show mapped copy such as:
Short domain messages (e.g. "Team not found") are preserved.
🤝 Collaboration
Collaborated with: N/A
✅ Checklist
Made with Cursor
Summary by CodeRabbit