diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts
index 368cdeb..219da7c 100644
--- a/frontend/src/lib/auth.ts
+++ b/frontend/src/lib/auth.ts
@@ -26,7 +26,10 @@ function metadataString(user: SupabaseUser, ...keys: string[]): string | undefin
}
export function toAppUser(user: SupabaseUser): User {
- const handle = metadataString(user, 'user_name', 'preferred_username') ?? user.email?.split('@')[0] ?? user.id
+ // The auth trigger stores profile handles lowercase (profiles_handle_format), while GitHub's
+ // user_name keeps its original casing. Handles are compared exactly everywhere (profile routes,
+ // owner filters), so the session handle must be derived the same way the trigger does.
+ const handle = (metadataString(user, 'user_name', 'preferred_username') ?? user.email?.split('@')[0] ?? user.id).toLowerCase()
return {
id: user.id,
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 8642555..9f9f1df 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -1,4 +1,4 @@
-import { Link, useParams, useSearchParams } from 'react-router-dom'
+import { Link, Navigate, useParams, useSearchParams } from 'react-router-dom'
import { ExternalLink, GitFork } from 'lucide-react'
import { Skeleton } from '@/components/ui/skeleton'
import { UserAvatar } from '@/components/UserAvatar'
@@ -33,11 +33,17 @@ export default function Profile() {
const [sp, setSp] = useSearchParams()
const tab = (sp.get('tab') as Tab) || 'rigs'
const cat = useCatalog()
+ // Handles are stored lowercase, but profile URLs are often typed or shared with
+ // GitHub's casing (e.g. /u/SergiioB). Resolve against the lowercase handle and
+ // canonicalize the address bar without dropping the tab query.
+ const canonical = handle.toLowerCase()
const data = useAsync(
- () => Promise.all([api.user(handle), api.userRigs(handle), api.userResults(handle), api.customRuntimes({ owner: handle })]),
- [handle],
+ () => Promise.all([api.user(canonical), api.userRigs(canonical), api.userResults(canonical), api.customRuntimes({ owner: canonical })]),
+ [canonical],
)
- usePageTitle(handle ? `@${handle}` : 'Profile')
+ usePageTitle(canonical ? `@${canonical}` : 'Profile')
+ if (canonical !== handle)
+ return
if (data.error)
return (