Skip to content

feat(frontend): Error classification & SectionError component (#791)#814

Merged
ericsocrat merged 8 commits intomainfrom
feat/791-error-boundaries
Mar 11, 2026
Merged

feat(frontend): Error classification & SectionError component (#791)#814
ericsocrat merged 8 commits intomainfrom
feat/791-error-boundaries

Conversation

@ericsocrat
Copy link
Copy Markdown
Owner

Closes #791. Add classifyError() utility (network/auth/server/unknown), enhance ErrorBoundary with per-category illustrations and i18n, create SectionError standalone component, add 34 new tests, add i18n keys for en/pl/de.

- Add classifyError() utility (network/auth/server/unknown categories)
- Enhance ErrorBoundary PageFallback with per-category illustrations and i18n
- Auth errors show 'Sign in' link; network errors show 'offline' illustration
- Create SectionError standalone component for TanStack Query error states
- Add i18n keys for all error categories in en/pl/de dictionaries
- Tests: error-classifier (19), SectionError (9), ErrorBoundary classification (6)
Copilot AI review requested due to automatic review settings March 10, 2026 19:49
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tryvit Ready Ready Preview, Comment Mar 11, 2026 3:32am

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 10, 2026

Bundle Size Report

Metric Value
Main baseline 3568 KB
This PR 3575 KB
Delta +7 KB (+0.2%)
JS chunks 136
Hard limit 4000 KB

✅ Bundle size is within acceptable limits.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds user-friendly error categorization to the frontend so error UIs (page-level and section-level) can show clearer, localized messaging and recovery actions.

Changes:

  • Introduces classifyError() utility to map errors into network | auth | server | unknown.
  • Enhances ErrorBoundary with category-specific i18n copy, illustrations, and an auth-specific “Sign in” CTA.
  • Adds SectionError inline component for TanStack Query-style error states, plus new tests and i18n keys (en/pl/de).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/src/lib/error-classifier.ts New error classification helper used by error UIs.
frontend/src/lib/error-classifier.test.ts Unit tests for classifyError() categorization + priority rules.
frontend/src/components/common/index.ts Re-exports SectionError from common components barrel.
frontend/src/components/common/SectionError.tsx New inline error card component with retry + i18n.
frontend/src/components/common/SectionError.test.tsx Tests for rendering, category behavior, label, and retry.
frontend/src/components/common/ErrorBoundary.tsx Category-aware page fallback UI + auth-specific CTA.
frontend/src/components/common/ErrorBoundary.test.tsx Adds tests verifying category-specific rendering and links.
frontend/messages/en.json Adds new errorBoundary.* and sectionError.* keys.
frontend/messages/pl.json Adds Polish translations for new error keys.
frontend/messages/de.json Adds German translations for new error keys.
CHANGELOG.md Documents newly added error classification + UI components.

Comment on lines +71 to +75
<button
onClick={onRetry}
className={buttonClasses("primary", "sm")}
data-testid="section-error-retry"
>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This retry control is a plain without an explicit type. When SectionError is rendered inside a (e.g., within a settings page section), the default type="submit" can cause accidental form submission. Add type="button" to make the intent explicit.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +123
<a
href="/auth/login"
className={buttonClasses("secondary", "md")}
>
{t("errorBoundary.signIn")}
</a>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

Copilot uses AI. Check for mistakes.
Comment on lines 111 to 116
<button
onClick={onReset}
className={buttonClasses("primary", "md")}
>
{t("common.tryAgain")}
</button>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This "Try again" control is a plain without an explicit type. If this boundary ever renders inside a , the default type="submit" can trigger unintended submissions. Set type="button" for non-submit buttons here (and in similar fallbacks) to avoid that behavior.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 11, 2026 02:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment on lines +71 to +78
<button
onClick={onRetry}
className={buttonClasses("primary", "sm")}
data-testid="section-error-retry"
>
<RefreshCw size={14} className="mr-1 inline" aria-hidden="true" />
{t("common.tryAgain")}
</button>
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The retry <button> defaults to type="submit", which can accidentally submit a surrounding form if SectionError is rendered inside one. Add type="button" to make the retry action non-submitting by default (consistent with other common components like Alert/Toggle/PrintButton).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 11, 2026 03:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment on lines +48 to +52
export function classifyError(error: Error): ErrorCategory {
const msg = error.message.toLowerCase();
const name = error.name.toLowerCase();
const combined = `${name} ${msg}`;

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Given this utility is intended to classify errors coming from various sources (including query libraries where the error is frequently typed as unknown), accepting only Error forces callers to cast and can be awkward at call sites. Consider changing the signature to accept unknown and normalizing to a message/name internally (e.g., handle non-Error thrown values) so both ErrorBoundary and SectionError can pass through query errors without unsafe casts.

Suggested change
export function classifyError(error: Error): ErrorCategory {
const msg = error.message.toLowerCase();
const name = error.name.toLowerCase();
const combined = `${name} ${msg}`;
export function classifyError(error: unknown): ErrorCategory {
let rawMessage = "";
let rawName = "";
if (error instanceof Error) {
rawMessage = error.message || "";
rawName = error.name || "";
} else if (typeof error === "string") {
rawMessage = error;
} else if (error && typeof error === "object") {
const maybeMessage = (error as { message?: unknown }).message;
const maybeName = (error as { name?: unknown }).name;
if (typeof maybeMessage === "string") {
rawMessage = maybeMessage;
}
if (typeof maybeName === "string") {
rawName = maybeName;
}
} else if (error != null) {
rawMessage = String(error);
}
const msg = rawMessage.toLowerCase();
const name = rawName.toLowerCase();
const combined = `${name} ${msg}`.trim();

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +40
const AUTH_PATTERNS = [
"jwt expired",
"jwt",
"not authenticated",
"unauthorized",
"403",
"401",
"auth",
"permission denied",
"access denied",
"invalid login",
"session expired",
"refresh_token",
"pgrst301",
] as const;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Using substring matching for status codes (e.g., \"401\", \"403\") can misclassify unrelated errors that happen to contain those digits as part of a larger number/token (e.g., \"1401\", \"E4012\"). To reduce false positives, prefer regex-based matching with boundaries for HTTP codes (and keep string patterns for phrases), or split auth detection into more specific checks.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +61
if (NETWORK_PATTERNS.some((p) => combined.includes(p))) {
return "network";
}

// Auth/permission errors
if (AUTH_PATTERNS.some((p) => combined.includes(p))) {
return "auth";
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Using substring matching for status codes (e.g., \"401\", \"403\") can misclassify unrelated errors that happen to contain those digits as part of a larger number/token (e.g., \"1401\", \"E4012\"). To reduce false positives, prefer regex-based matching with boundaries for HTTP codes (and keep string patterns for phrases), or split auth detection into more specific checks.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 11, 2026 03:24
@ericsocrat ericsocrat merged commit 69089dd into main Mar 11, 2026
18 of 19 checks passed
@ericsocrat ericsocrat deleted the feat/791-error-boundaries branch March 11, 2026 03:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

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.

feat(frontend): Consumer-friendly error boundaries — offline banner, component-level recovery, retry UX

2 participants