From 3261a822aae9207eaff28ce42887f1d0f68fbaa9 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 13 Mar 2026 00:51:07 +0100 Subject: [PATCH 1/3] add error page for improved error reporting and user experience --- .changeset/error_page_with_report.md | 5 + src/app/components/DefaultErrorPage.tsx | 116 ++++++++++++++++++ .../features/bug-report/BugReportModal.tsx | 6 +- src/app/pages/App.tsx | 58 +++++---- 4 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 .changeset/error_page_with_report.md create mode 100644 src/app/components/DefaultErrorPage.tsx diff --git a/.changeset/error_page_with_report.md b/.changeset/error_page_with_report.md new file mode 100644 index 000000000..3a619c8d1 --- /dev/null +++ b/.changeset/error_page_with_report.md @@ -0,0 +1,5 @@ +--- +sable: minor +--- + +added error page making it easier to report errors when they occur in the field diff --git a/src/app/components/DefaultErrorPage.tsx b/src/app/components/DefaultErrorPage.tsx new file mode 100644 index 000000000..edf8acf5f --- /dev/null +++ b/src/app/components/DefaultErrorPage.tsx @@ -0,0 +1,116 @@ +import { Box, Button, Dialog, Icon, Icons, Text, color, config } from 'folds'; +import { SplashScreen } from '$components/splash-screen'; +import { buildGitHubUrl } from '$features/bug-report/BugReportModal'; + +type ErrorPageProps = { + error: Error; +}; + +function createIssueUrl(error: Error): string { + const stacktrace = error.stack || 'No stacktrace available'; + + const automatedBugReport = `# Automated Bug Report +Error occurred in the application. + +## Error Message +\`\`\` +${error.message} +\`\`\` + +## Stacktrace +\`\`\` +${stacktrace} +\`\`\``; + + return buildGitHubUrl('bug', `Error: ${error.message}`, { context: automatedBugReport }); +} + +// This component is used as the fallback for the ErrorBoundary in App.tsx, which means it will be rendered whenever an uncaught error is thrown in any of the child components and not handled locally. +// It provides a user-friendly error message and options to report the issue or reload the page. +// Motivation of the design is to encourage users to report issues while also providing them with the necessary information to do so, and to give them an easy way to recover by reloading the page. +// Note: Since this component is rendered in response to an error, it should be as resilient as possible and avoid any complex logic or dependencies that could potentially throw additional errors. +export function ErrorPage({ error }: ErrorPageProps) { + return ( + + + + + + + + Oops! Something went wrong + + + An unexpected error occurred. Please try again. If it continues, report the issue on + our GitHub using the button below, it will include error details to help us + investigate. Thank you for helping improve the app. + + + + + {error.message} + + + + {error.stack} + + + + + + + + + + ); +} diff --git a/src/app/features/bug-report/BugReportModal.tsx b/src/app/features/bug-report/BugReportModal.tsx index 1ee788f05..2f90fda31 100644 --- a/src/app/features/bug-report/BugReportModal.tsx +++ b/src/app/features/bug-report/BugReportModal.tsx @@ -52,7 +52,11 @@ async function searchSimilarIssues(query: string, signal: AbortSignal): Promise< // Field IDs match the ids defined in .github/ISSUE_TEMPLATE/bug_report.yml // and feature_request.yml so GitHub pre-fills each form field directly. -function buildGitHubUrl(type: ReportType, title: string, fields: Record): string { +export function buildGitHubUrl( + type: ReportType, + title: string, + fields: Record +): string { const devLabel = IS_RELEASE_TAG ? '' : '-dev'; const buildLabel = BUILD_HASH ? ` (${BUILD_HASH})` : ''; const version = `v${APP_VERSION}${devLabel}${buildLabel}`; diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index bfe51f2a9..0408f38ea 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -3,11 +3,13 @@ import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProv import { RouterProvider } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ErrorBoundary } from 'react-error-boundary'; import { ClientConfigLoader } from '$components/ClientConfigLoader'; import { ClientConfigProvider } from '$hooks/useClientConfig'; import { ScreenSizeProvider, useScreenSize } from '$hooks/useScreenSize'; import { useCompositionEndTracking } from '$hooks/useComposingCheck'; +import { ErrorPage } from '$components/DefaultErrorPage'; import { ConfigConfigError, ConfigConfigLoading } from './ConfigConfig'; import { FeatureCheck } from './FeatureCheck'; import { createRouter } from './Router'; @@ -21,33 +23,35 @@ function App() { const portalContainer = document.getElementById('portalContainer') ?? undefined; return ( - - - - - - } - error={(err, retry, ignore) => ( - - )} - > - {(clientConfig) => ( - - - - - - - - - )} - - - - - - + + + + + + + } + error={(err, retry, ignore) => ( + + )} + > + {(clientConfig) => ( + + + + + + + + + )} + + + + + + + ); } From 40950a728f7d0ad212d55c25598b0ffc65d47f42 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 14 Mar 2026 02:48:39 +0100 Subject: [PATCH 2/3] strip beepers per message profile on forward fixes https://github.com/SableClient/Sable/issues/254 --- src/app/components/message/modals/MessageForward.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/components/message/modals/MessageForward.tsx b/src/app/components/message/modals/MessageForward.tsx index 6575fbd93..1b2860ee9 100644 --- a/src/app/components/message/modals/MessageForward.tsx +++ b/src/app/components/message/modals/MessageForward.tsx @@ -211,13 +211,15 @@ export function MessageForwardInternal({ formatted_body: newBodyHtml, } : {}; + const baseContent = { ...mEvent.getContent() }; + delete baseContent['com.beeper.per_message_profile']; // remove per-message profile as that could confuse clients in the target room let content; // handle privacy stuff if (isPrivate) { // if the message is from a private room, we should strip any media or mentions to avoid leaking information to the target room // we can still include the original message content in the body of the message, so we'll just use a fallback text/plain content with the original message body content = { - ...mEvent.getContent(), + ...baseContent, 'm.relates_to': null, // remove any relations to avoid confusion in the target room 'm.mentions': null, // remove mentions to avoid leaking information about users in the original room ...forwardedTextContent, @@ -230,7 +232,7 @@ export function MessageForwardInternal({ }; } else { content = { - ...mEvent.getContent(), + ...baseContent, ...forwardedTextContent, 'm.relates_to': { rel_type: 'm.reference', From 2e2e1ae06baf64080a944bff731e233c40bb4ef7 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 14 Mar 2026 02:52:31 +0100 Subject: [PATCH 3/3] added changeset for the fix of https://github.com/SableClient/Sable/issues/254 --- .changeset/strip_beeper_pmp_on_forward.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strip_beeper_pmp_on_forward.md diff --git a/.changeset/strip_beeper_pmp_on_forward.md b/.changeset/strip_beeper_pmp_on_forward.md new file mode 100644 index 000000000..55b749923 --- /dev/null +++ b/.changeset/strip_beeper_pmp_on_forward.md @@ -0,0 +1,5 @@ +--- +sable: patch +--- + +removed forwarding of beeper's per message profile, as this might confuse clients