From fe969a7d9673371e909676cb680afd7916891cbd Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sat, 25 Apr 2026 23:17:19 +0200 Subject: [PATCH] =?UTF-8?q?fix(web):=20friend-add=20error=20toasts=20surfa?= =?UTF-8?q?ce=20raw=20codes=20=E2=80=94=20read=20err.message,=20not=20err.?= =?UTF-8?q?body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The T19/T20 catch blocks looked for an `.body` property on thrown errors to extract the structured error code. The shared API client (api/client.ts:298) actually throws `new Error(body.error)` — the code lives in `err.message`, and there's no `.body` attached. Live E2E (T22 scenario 2) caught this: typing alice@orbit against an awaiting_approval peer surfaced the raw code 'peer_pending_approval' as the toast text instead of the human-readable mapServerErrorToMessage output. Same defect would have hit every server-error toast on both FriendsPage (AddFriend + UserDiscoverCard) and UserProfileModal. Catch blocks now use err.message as both the code and the fallback text; the inline comment points at the API client throw site so the contract is documented at the consumer. --- .../web/src/components/chat/FriendsPage.tsx | 28 ++++++------------- .../components/modals/UserProfileModal.tsx | 14 ++++------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx index b3d78fc3..39df158b 100644 --- a/packages/web/src/components/chat/FriendsPage.tsx +++ b/packages/web/src/components/chat/FriendsPage.tsx @@ -476,16 +476,12 @@ function AddFriendTab({ addToast('Friend request sent!', 'success'); setQuery(''); } catch (err) { - const errorBody = (err as { body?: unknown })?.body; - let code: string | undefined; - let message: string | undefined; - if (errorBody && typeof errorBody === 'object') { - code = (errorBody as { error?: string }).error; - message = (errorBody as { message?: string }).message; - } - // Fall back to the Error message if the server didn't send a structured body. - const fallback = message ?? (err instanceof Error ? err.message : undefined); - addToast(mapServerErrorToMessage(code, fallback, query.trim()), 'warning'); + // The shared API client throws `new Error(body.error)` for non-2xx + // responses (api/client.ts:298), so `err.message` carries the server's + // error code (e.g. 'peer_pending_approval'). It also doubles as fallback + // text if the code is unrecognized by mapServerErrorToMessage. + const code = err instanceof Error ? err.message : undefined; + addToast(mapServerErrorToMessage(code, code, query.trim()), 'warning'); } finally { setDirectAddLoading(false); } @@ -643,15 +639,9 @@ function UserDiscoverCard({ const requestId = await sendFriendRequest(username); onRelationshipChange(user.id, user._instanceOrigin, 'outbound_pending', requestId); } catch (err) { - const errorBody = (err as { body?: unknown })?.body; - let code: string | undefined; - let message: string | undefined; - if (errorBody && typeof errorBody === 'object') { - code = (errorBody as { error?: string }).error; - message = (errorBody as { message?: string }).message; - } - const fallback = message ?? (err instanceof Error ? err.message : 'Failed to send request'); - setError(mapServerErrorToMessage(code, fallback, username)); + // See handleDirectAdd above — err.message is the server error code. + const code = err instanceof Error ? err.message : undefined; + setError(mapServerErrorToMessage(code, code ?? 'Failed to send request', username)); } finally { setActionLoading(false); } diff --git a/packages/web/src/components/modals/UserProfileModal.tsx b/packages/web/src/components/modals/UserProfileModal.tsx index 09f0d26b..1e3a6f53 100644 --- a/packages/web/src/components/modals/UserProfileModal.tsx +++ b/packages/web/src/components/modals/UserProfileModal.tsx @@ -186,15 +186,11 @@ export function UserProfileModal() { try { await sendFriendRequest(user.username); } catch (err) { - const errorBody = (err as { body?: unknown })?.body; - let code: string | undefined; - let message: string | undefined; - if (errorBody && typeof errorBody === 'object') { - code = (errorBody as { error?: string }).error; - message = (errorBody as { message?: string }).message; - } - const fallback = message ?? (err instanceof Error ? err.message : undefined); - addToast(mapServerErrorToMessage(code, fallback, user.username), 'warning'); + // The shared API client throws `new Error(body.error)` for non-2xx + // responses (api/client.ts:298), so err.message carries the server's + // error code (e.g. 'peer_pending_approval'). + const code = err instanceof Error ? err.message : undefined; + addToast(mapServerErrorToMessage(code, code, user.username), 'warning'); } finally { setFriendActionLoading(false); }