csr tangled client in solid-js
15

Configure Feed

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

improve scopemissingerror messages

dawn (May 30, 2026, 7:21 AM +0300) d49fde26 eccaabee

+45 -20
+2 -2
src/layout.tsx
··· 8 8 import { clearApiCaches, resolveActor } from './lib/api/identity'; 9 9 import { useAuth } from './lib/auth'; 10 10 import { useLiveEvents, type LiveEvent } from './lib/live-events'; 11 - import { formatRelativeTime } from './lib/repo-utils'; 11 + import { formatRelativeTime, getErrorMessage } from './lib/repo-utils'; 12 12 import { useAppSettings } from './lib/settings'; 13 13 14 14 const TOPBAR_MENU_SELECTOR = 'details[data-topbar-menu]'; ··· 63 63 reloadAppviewBackedQueries(); 64 64 } catch (cause) { 65 65 setSaved(false); 66 - setError(cause instanceof Error ? cause.message : 'Invalid appview URL'); 66 + setError(getErrorMessage(cause)); 67 67 } 68 68 }; 69 69
+4 -4
src/lib/auth.tsx
··· 12 12 import { createContext, createSignal, onMount, useContext } from 'solid-js'; 13 13 14 14 import { TANGLED_OAUTH_SCOPE, identityResolver } from './api'; 15 + import { getErrorMessage } from './repo-utils'; 15 16 16 17 const CURRENT_DID_KEY = 'untangled.currentDid'; 17 18 ··· 68 69 localStorage.removeItem(CURRENT_DID_KEY); 69 70 setAgent(null); 70 71 setCurrentDid(null); 71 - setError(cause instanceof Error ? cause.message : 'Failed to restore session'); 72 + setError(getErrorMessage(cause)); 72 73 } finally { 73 74 setIsLoading(false); 74 75 } ··· 100 101 101 102 window.location.assign(url.toString()); 102 103 } catch (cause) { 103 - setError(cause instanceof Error ? cause.message : 'Failed to start OAuth sign-in'); 104 + setError(getErrorMessage(cause)); 104 105 throw cause; 105 106 } 106 107 }; ··· 123 124 ? (state as { returnTo: string }).returnTo 124 125 : '/'; 125 126 } catch (cause) { 126 - const message = cause instanceof Error ? cause.message : 'OAuth sign-in failed'; 127 - setError(message); 127 + setError(getErrorMessage(cause)); 128 128 throw cause; 129 129 } finally { 130 130 setIsLoading(false);
+10 -2
src/lib/repo-utils.ts
··· 86 86 return rtf.format(Math.round(seconds / 31_536_000), 'year'); 87 87 }; 88 88 89 - export const getErrorMessage = (cause: unknown): string => 90 - cause instanceof Error ? cause.message : 'Unknown error'; 89 + export const getErrorMessage = (cause: unknown): string => { 90 + if (cause instanceof Error) { 91 + const message = cause.message; 92 + if (cause.name === 'ScopeMissingError' || message.includes('ScopeMissingError')) { 93 + return `please relogin (${message})`; 94 + } 95 + return message; 96 + } 97 + return 'Unknown error'; 98 + }; 91 99 92 100 export const formatBytes = (value: number): string => { 93 101 if (value < 1024) {
+3 -3
src/pages/home.tsx
··· 24 24 import { listRepoRecords, type RepoRecord } from '../lib/api/repos'; 25 25 import { useAuth } from '../lib/auth'; 26 26 import { useLiveEvents, type LiveEvent } from '../lib/live-events'; 27 - import { formatRelativeTime, loadRecentRepos, loadRecentIssuesPulls } from '../lib/repo-utils'; 27 + import { formatRelativeTime, getErrorMessage, loadRecentRepos, loadRecentIssuesPulls } from '../lib/repo-utils'; 28 28 29 29 const SAMPLE_REPOS: Array<{ 30 30 owner: string; ··· 480 480 > 481 481 <Show 482 482 when={!ownReposQuery.error} 483 - fallback={<ErrorState message={ownReposQuery.error instanceof Error ? ownReposQuery.error.message : 'Failed to load activity'} />} 483 + fallback={<ErrorState message={getErrorMessage(ownReposQuery.error)} />} 484 484 > 485 485 <Show 486 486 when={activityItems().length > 0} ··· 596 596 > 597 597 <Show 598 598 when={!followingQuery.error} 599 - fallback={<ErrorState message={followingQuery.error instanceof Error ? followingQuery.error.message : 'Failed to load timeline'} />} 599 + fallback={<ErrorState message={getErrorMessage(followingQuery.error)} />} 600 600 > 601 601 <Show 602 602 when={(followingQuery.data?.activity.length ?? 0) > 0}
+18 -1
src/pages/profile.tsx
··· 41 41 import { listRepoRecords, getRepoByDid, type RepoContext } from '../lib/api/repos'; 42 42 import { useAuth } from '../lib/auth'; 43 43 import { listAllAppviewRecords } from '../lib/api/appview'; 44 - import { formatRelativeTime } from '../lib/repo-utils'; 44 + import { formatRelativeTime, getErrorMessage } from '../lib/repo-utils'; 45 45 import { ShTangledFeedStar, ShTangledActorProfile } from '@atcute/tangled'; 46 46 47 47 const ProfileTabs: Component<{ ··· 871 871 const [repoSearch, setRepoSearch] = createSignal(''); 872 872 const [actionLoading, setActionLoading] = createSignal(false); 873 873 const [isRedirecting, setIsRedirecting] = createSignal(false); 874 + const [error, setError] = createSignal<string | null>(null); 874 875 875 876 const actorParam = () => params.actor as string; 876 877 877 878 createEffect(() => { 878 879 actorParam(); 879 880 setIsRedirecting(false); 881 + setError(null); 880 882 }); 881 883 882 884 const isDid = () => actorParam().startsWith('did:'); ··· 905 907 const err = repoDidQuery.error; 906 908 if (err) { 907 909 console.error('Failed to resolve DID as repo:', err); 910 + setError(getErrorMessage(err)); 908 911 } 909 912 }); 910 913 ··· 1019 1022 if (!agent || !profileDid) return; 1020 1023 1021 1024 setActionLoading(true); 1025 + setError(null); 1022 1026 try { 1023 1027 const rkey = followRecordRkey(); 1024 1028 if (rkey) { ··· 1030 1034 queryClient.invalidateQueries({ queryKey: ['profile-followers', profileDid] }); 1031 1035 } catch (e) { 1032 1036 console.error('Failed to toggle follow status', e); 1037 + setError(getErrorMessage(e)); 1033 1038 } finally { 1034 1039 setActionLoading(false); 1035 1040 } ··· 1039 1044 const agent = auth.agent(); 1040 1045 if (!agent) return; 1041 1046 1047 + setError(null); 1042 1048 try { 1043 1049 const viewerFollows = viewerFollowsQuery.data ?? []; 1044 1050 const existing = viewerFollows.find((f) => f.value.subject === did); ··· 1053 1059 } 1054 1060 } catch (e) { 1055 1061 console.error('Failed to toggle follow status for list item', e); 1062 + setError(getErrorMessage(e)); 1056 1063 } 1057 1064 }; 1058 1065 ··· 1070 1077 if (!agent || !profileDid) return; 1071 1078 1072 1079 setVouchActionLoading(true); 1080 + setError(null); 1073 1081 try { 1074 1082 if (kind === 'none') { 1075 1083 await deleteVouchRecord(agent, profileDid); ··· 1079 1087 queryClient.invalidateQueries({ queryKey: ['vouch-record', auth.currentDid(), profileDid] }); 1080 1088 } catch (e) { 1081 1089 console.error('Failed to submit vouch status', e); 1090 + setError(getErrorMessage(e)); 1082 1091 } finally { 1083 1092 setVouchActionLoading(false); 1084 1093 } ··· 1092 1101 if (!agent || !profileDid) return; 1093 1102 1094 1103 setProfileUpdateLoading(true); 1104 + setError(null); 1095 1105 try { 1096 1106 await putActorProfile(agent, profile); 1097 1107 queryClient.invalidateQueries({ queryKey: ['profile-record', profileDid] }); 1098 1108 } catch (e) { 1099 1109 console.error('Failed to update profile record', e); 1110 + setError(getErrorMessage(e)); 1100 1111 } finally { 1101 1112 setProfileUpdateLoading(false); 1102 1113 } ··· 1221 1232 openIssueCount={openIssueCount()} 1222 1233 closedIssueCount={closedIssueCount()} 1223 1234 /> 1235 + 1236 + <Show when={error()}> 1237 + <div class="mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-sm text-red-600 dark:text-red-400"> 1238 + {error()} 1239 + </div> 1240 + </Show> 1224 1241 </div> 1225 1242 1226 1243 <Switch>
+4 -4
src/pages/repo/issues.tsx
··· 232 232 await client.invalidateQueries({ queryKey: issuesQueryKey(repo.repoDid) }); 233 233 navigate(issueHref(repo, created.uri)); 234 234 } catch (cause) { 235 - setError(cause instanceof Error ? cause.message : 'Failed to create issue'); 235 + setError(getErrorMessage(cause)); 236 236 } finally { 237 237 setSubmitting(false); 238 238 } ··· 385 385 await client.invalidateQueries({ queryKey: issueQueryKey(repo.repoDid, issueRef()), refetchType: 'none' }); 386 386 await client.invalidateQueries({ queryKey: issueQueryKey(repo.repoDid, parseAtUri(detail.issue.uri).rkey), refetchType: 'none' }); 387 387 } catch (cause) { 388 - setError(cause instanceof Error ? cause.message : 'Failed to update issue state'); 388 + setError(getErrorMessage(cause)); 389 389 } finally { 390 390 setWorkingAction(null); 391 391 } ··· 407 407 setComment(''); 408 408 await withIssueInvalidation(repo, issueRef(), detail.issue.uri); 409 409 } catch (cause) { 410 - setError(cause instanceof Error ? cause.message : 'Failed to create comment'); 410 + setError(getErrorMessage(cause)); 411 411 } finally { 412 412 setWorkingAction(null); 413 413 } ··· 436 436 setReplyingTo(null); 437 437 await withIssueInvalidation(repo, issueRef(), detail.issue.uri); 438 438 } catch (cause) { 439 - setError(cause instanceof Error ? cause.message : 'Failed to create reply'); 439 + setError(getErrorMessage(cause)); 440 440 } finally { 441 441 setWorkingAction(null); 442 442 }
+4 -4
src/pages/repo/pulls.tsx
··· 479 479 await client.invalidateQueries({ queryKey: pullsQueryKey(repo.repoDid) }); 480 480 navigate(pullHref(repo, created.uri)); 481 481 } catch (cause) { 482 - setError(cause instanceof Error ? cause.message : 'Failed to create pull request'); 482 + setError(getErrorMessage(cause)); 483 483 } finally { 484 484 setSubmitting(false); 485 485 } ··· 1157 1157 updatePullStatusCaches(repo, pullRef(), detail.pull.uri, nextState); 1158 1158 await markPullQueriesStale(repo, pullRef(), detail.pull.uri); 1159 1159 } catch (cause) { 1160 - setError(cause instanceof Error ? cause.message : 'Failed to update pull state'); 1160 + setError(getErrorMessage(cause)); 1161 1161 } finally { 1162 1162 setWorkingAction(null); 1163 1163 } ··· 1200 1200 await client.invalidateQueries({ queryKey: ['repo-branches', repo.repoDid] }); 1201 1201 await client.invalidateQueries({ queryKey: ['repo-languages', repo.repoDid] }); 1202 1202 } catch (cause) { 1203 - setError(cause instanceof Error ? cause.message : 'Failed to merge pull request'); 1203 + setError(getErrorMessage(cause)); 1204 1204 } finally { 1205 1205 setWorkingAction(null); 1206 1206 } ··· 1229 1229 setComposerOpen(false); 1230 1230 await invalidate(repo, pullRef(), detail.pull.uri); 1231 1231 } catch (cause) { 1232 - setError(cause instanceof Error ? cause.message : 'Failed to create comment'); 1232 + setError(getErrorMessage(cause)); 1233 1233 } finally { 1234 1234 setWorkingAction(null); 1235 1235 }