feat: normalize login input (strip @ and at:// prefixes) (#613)

strips common prefixes from user input on the login page:
- `@user.bsky.social` → `user.bsky.social`
- `at://user.bsky.social` → `user.bsky.social`
- `at://did:plc:abc123` → `did:plc:abc123`

the more complex URL handling and .bsky.social auto-append
need backend support and are deferred for now.

inspired by https://ngerakines.leaflet.pub/3ma7hed2kdk2x

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

authored by zzstoatzz.io Claude Opus 4.5 and committed by GitHub a9a479e1 1d64e2f2

Changed files
+25 -1
frontend
src
routes
login
+25 -1
frontend/src/routes/login/+page.svelte
··· 8 8 let showHandleInfo = $state(false); 9 9 let showPdsInfo = $state(false); 10 10 11 + /** 12 + * normalize user input to a valid identifier for OAuth 13 + * 14 + * accepts: 15 + * - handles: "user.bsky.social", "@user.bsky.social", "at://user.bsky.social" 16 + * - DIDs: "did:plc:abc123", "at://did:plc:abc123" 17 + */ 18 + function normalizeInput(input: string): string { 19 + let value = input.trim(); 20 + 21 + // strip at:// prefix (valid for both handles and DIDs per AT-URI spec) 22 + if (value.startsWith('at://')) { 23 + value = value.slice(5); 24 + } 25 + 26 + // strip @ prefix from handles 27 + if (value.startsWith('@')) { 28 + value = value.slice(1); 29 + } 30 + 31 + return value; 32 + } 33 + 11 34 function startOAuth(e: SubmitEvent) { 12 35 e.preventDefault(); 13 36 if (!handle.trim()) return; 14 37 loading = true; 15 - window.location.href = `${API_URL}/auth/start?handle=${encodeURIComponent(handle)}`; 38 + const normalized = normalizeInput(handle); 39 + window.location.href = `${API_URL}/auth/start?handle=${encodeURIComponent(normalized)}`; 16 40 } 17 41 18 42 function handleSelect(selected: string) {