[READ-ONLY] Mirror of https://github.com/aaronateataco/ermine. A stoat web client erminechat.vercel.app
0

Configure Feed

Select the types of activity you want to include in your feed.

Add crash fallback screen with error boundary

Fascinating Pistachio (Feb 14, 2026, 2:49 PM UTC) 8ea19904 ae54c796

+49 -1
+49 -1
src/App.jsx
··· 157 157 </div> 158 158 ); 159 159 160 + 161 + class AppErrorBoundary extends React.Component { 162 + constructor(props) { 163 + super(props); 164 + this.state = { hasError: false }; 165 + } 166 + 167 + static getDerivedStateFromError() { 168 + return { hasError: true }; 169 + } 170 + 171 + componentDidCatch(error) { 172 + console.error('Ermine crashed:', error); 173 + } 174 + 175 + render() { 176 + if (this.state.hasError) { 177 + return ( 178 + <div className="grid min-h-screen place-items-center bg-[#1e1f22] p-6 text-gray-100"> 179 + <div className="w-full max-w-lg rounded-2xl border border-[#202225] bg-[#2b2d31] p-8 text-center shadow-2xl"> 180 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-[#949ba4]">Well, this is awkward</p> 181 + <h1 className="mt-3 text-2xl font-bold text-white">Looks like Ermine crashed.</h1> 182 + <p className="mt-2 text-sm text-gray-300">Try reloading the app. If it keeps happening, relog and reconnect to stoat.chat.</p> 183 + <button 184 + className="mt-6 rounded-md bg-[#5865f2] px-4 py-2 text-sm font-semibold text-white hover:bg-[#4956d8]" 185 + onClick={() => window.location.reload()} 186 + type="button" 187 + > 188 + Reload Ermine 189 + </button> 190 + </div> 191 + </div> 192 + ); 193 + } 194 + 195 + return this.props.children; 196 + } 197 + } 198 + 160 199 const Message = ({ message, users, channels, me, onUserClick, cdnUrl, onToggleReaction, onReply, replyTarget }) => { 161 200 const authorId = typeof message.author === 'string' ? message.author : message.author?._id; 162 201 const author = users[authorId] || (typeof message.author === 'object' ? message.author : null) || { username: 'Unknown user' }; ··· 217 256 ); 218 257 }; 219 258 220 - export default function App() { 259 + function AppShell() { 221 260 const [view, setView] = useState('loading'); 222 261 const [status, setStatus] = useState('disconnected'); 223 262 const [config, setConfig] = useState({ apiUrl: DEFAULT_API_URL, wsUrl: DEFAULT_WS_URL, cdnUrl: DEFAULT_CDN_URL }); ··· 1071 1110 </div> 1072 1111 ); 1073 1112 } 1113 + 1114 + export default function App() { 1115 + return ( 1116 + <AppErrorBoundary> 1117 + <AppShell /> 1118 + </AppErrorBoundary> 1119 + ); 1120 + } 1121 +