Skip to content

Add plain-language context to error notifications - #1020

Open
FlatterAtMainz wants to merge 2 commits into
jaedb:masterfrom
FlatterAtMainz:fix/error-message-context
Open

Add plain-language context to error notifications#1020
FlatterAtMainz wants to merge 2 commits into
jaedb:masterfrom
FlatterAtMainz:fix/error-message-context

Conversation

@FlatterAtMainz

Copy link
Copy Markdown

Errors surfaced through handleException often ended up either blank or as a bare technical fragment (e.g. "Could not load your profile" with no explanation, or a raw "SyntaxError: JSON.parse: unexpected end of input"). Add a humanizeError util that recognises common failure shapes (invalid/empty JSON responses, network failures, expired/revoked OAuth grants, known HTTP status codes) and prefixes the description with a plain-language explanation, applied centrally in the HANDLE_EXCEPTION handler so every existing call site benefits.

Also:

  • handleException now falls back to a plain Error object's .message for the description, and no longer throws when data.error is undefined (message derivation had the same unguarded access).
  • Wrap the JSON.parse of xhr.responseText in HANDLE_EXCEPTION in a try/catch - a non-JSON error response would otherwise throw inside the exception handler itself.
  • Spotify getMe() and refreshToken() failures now get specific messages pointing at expired/revoked authorization, since that was the actual root cause behind the vague errors we hit in practice.

Errors surfaced through handleException often ended up either blank
or as a bare technical fragment (e.g. "Could not load your profile"
with no explanation, or a raw "SyntaxError: JSON.parse: unexpected
end of input"). Add a humanizeError util that recognises common
failure shapes (invalid/empty JSON responses, network failures,
expired/revoked OAuth grants, known HTTP status codes) and prefixes
the description with a plain-language explanation, applied centrally
in the HANDLE_EXCEPTION handler so every existing call site benefits.

Also:
- handleException now falls back to a plain Error object's .message
  for the description, and no longer throws when data.error is
  undefined (message derivation had the same unguarded access).
- Wrap the JSON.parse of xhr.responseText in HANDLE_EXCEPTION in a
  try/catch - a non-JSON error response would otherwise throw inside
  the exception handler itself.
- Spotify getMe() and refreshToken() failures now get specific
  messages pointing at expired/revoked authorization, since that was
  the actual root cause behind the vague errors we hit in practice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@jaedb jaedb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Nice work! Would be great to explore the viability of moving these human-friendly errors into the translation file, making it accessible for translation.

Comment on lines 125 to +336
@@ -329,8 +331,9 @@ export function getMe() {
},
(error) => {
dispatch(coreActions.handleException(
'Could not load your profile',
'Could not load your Spotify profile',
error,
'This usually means your Spotify authorization has expired or is invalid. Try reconnecting Spotify in Settings.',

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

These human-friendly errors would be great to have in the translation file

Comment thread src/js/services/core/actions.js Outdated
if (data.message) {
message = data.message;
} else if (data.error.message) {
} else if (data.error && data.error.message) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This could simply be data.error?.message, no?

Comment thread src/js/util/errors.js Outdated
Comment on lines +3 to +13
0: 'Could not reach the server. Check your network connection, or that the server is running.',
400: 'The request was invalid or rejected by the server.',
401: 'You are not authenticated, or your session has expired.',
403: 'You do not have permission to do that.',
404: 'The requested resource could not be found. It may have been removed or renamed.',
408: 'The request timed out.',
429: 'Too many requests were sent in a short period. Wait a moment and try again.',
500: 'The server encountered an internal error.',
502: 'The server is temporarily unavailable (bad gateway).',
503: 'The server is temporarily unavailable.',
504: 'The server took too long to respond (gateway timeout).',

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

These too, could be in the translation file. It may be tricky, given the execution level to apply the t() method though.

… optional chaining

- Humanized error explanations now live under errors.* in en.yaml
  (via i18n()) instead of being hardcoded in JS, so they're
  translatable like the rest of the UI.
- Replace `x && x.y` null checks with optional chaining per review
  feedback.
@FlatterAtMainz

Copy link
Copy Markdown
Author

Thanks for the review! Pushed 7b3a2d63 addressing both points:

  • Moved the human-friendly explanations into en.yaml under errors.* (e.g. errors.invalid_response, errors.network_unreachable, errors.authorization_expired, errors.request_timeout, errors.http_status.<code>), looked up via the existing i18n() helper instead of being hardcoded in errors.js. They pick up translations the same way the rest of the UI does.
  • Replaced the data.error && data.error.message checks with data.error?.message (and the same pattern in middleware.js).

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.

2 participants