WIP PWA for Grain

docs: add OAuth callback route implementation plan

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+211
+211
docs/plans/2026-01-02-oauth-callback-route.md
··· 1 + # OAuth Callback Route Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Add a dedicated `/oauth/callback` route so users can log in from any page and return to where they were. 6 + 7 + **Architecture:** Store the current path in sessionStorage before OAuth redirect, then read it back after the callback completes and navigate there. 8 + 9 + **Tech Stack:** Lit web components, quickslice-client-js OAuth 10 + 11 + --- 12 + 13 + ### Task 1: Create OAuth Callback Page Component 14 + 15 + **Files:** 16 + - Create: `src/components/pages/grain-oauth-callback.js` 17 + 18 + **Step 1: Create the callback component** 19 + 20 + ```javascript 21 + import { LitElement, html, css } from 'lit'; 22 + import '../atoms/grain-spinner.js'; 23 + 24 + export class GrainOAuthCallback extends LitElement { 25 + static styles = css` 26 + :host { 27 + display: flex; 28 + flex-direction: column; 29 + align-items: center; 30 + justify-content: center; 31 + min-height: 100%; 32 + gap: var(--space-md); 33 + } 34 + p { 35 + color: var(--color-text-secondary); 36 + font-size: var(--font-size-sm); 37 + } 38 + `; 39 + 40 + render() { 41 + return html` 42 + <grain-spinner size="32"></grain-spinner> 43 + <p>Signing in...</p> 44 + `; 45 + } 46 + } 47 + 48 + customElements.define('grain-oauth-callback', GrainOAuthCallback); 49 + ``` 50 + 51 + **Step 2: Verify file exists** 52 + 53 + Run: `cat src/components/pages/grain-oauth-callback.js` 54 + Expected: File contents match above 55 + 56 + **Step 3: Commit** 57 + 58 + ```bash 59 + git add src/components/pages/grain-oauth-callback.js 60 + git commit -m "feat: add OAuth callback page component" 61 + ``` 62 + 63 + --- 64 + 65 + ### Task 2: Register the OAuth Callback Route 66 + 67 + **Files:** 68 + - Modify: `src/components/pages/grain-app.js` 69 + 70 + **Step 1: Add import for callback component** 71 + 72 + Add after line 17 (after grain-copyright import): 73 + ```javascript 74 + import './grain-oauth-callback.js'; 75 + ``` 76 + 77 + **Step 2: Register the route** 78 + 79 + Add after line 67 (before the `*` wildcard route): 80 + ```javascript 81 + .register('/oauth/callback', 'grain-oauth-callback') 82 + ``` 83 + 84 + **Step 3: Verify build passes** 85 + 86 + Run: `npm run build` 87 + Expected: Build succeeds 88 + 89 + **Step 4: Commit** 90 + 91 + ```bash 92 + git add src/components/pages/grain-app.js 93 + git commit -m "feat: register /oauth/callback route" 94 + ``` 95 + 96 + --- 97 + 98 + ### Task 3: Store Return URL Before OAuth Redirect 99 + 100 + **Files:** 101 + - Modify: `src/services/auth.js` 102 + 103 + **Step 1: Update login method to store return URL** 104 + 105 + Replace the login method (lines 58-60): 106 + 107 + ```javascript 108 + async login(handle) { 109 + sessionStorage.setItem('oauth_return_url', window.location.pathname); 110 + await this.#client.loginWithRedirect({ handle }); 111 + } 112 + ``` 113 + 114 + **Step 2: Verify build passes** 115 + 116 + Run: `npm run build` 117 + Expected: Build succeeds 118 + 119 + **Step 3: Commit** 120 + 121 + ```bash 122 + git add src/services/auth.js 123 + git commit -m "feat: store return URL before OAuth redirect" 124 + ``` 125 + 126 + --- 127 + 128 + ### Task 4: Navigate to Return URL After OAuth Callback 129 + 130 + **Files:** 131 + - Modify: `src/services/auth.js` 132 + 133 + **Step 1: Add router import at top of file** 134 + 135 + Add after line 1: 136 + ```javascript 137 + import { router } from '../router.js'; 138 + ``` 139 + 140 + **Step 2: Update callback handling to navigate to return URL** 141 + 142 + Replace lines 18-22 (the callback handling block): 143 + 144 + ```javascript 145 + // Handle OAuth callback if present 146 + if (window.location.search.includes('code=')) { 147 + await this.#client.handleRedirectCallback(); 148 + const returnUrl = sessionStorage.getItem('oauth_return_url') || '/'; 149 + sessionStorage.removeItem('oauth_return_url'); 150 + router.replace(returnUrl); 151 + } 152 + ``` 153 + 154 + **Step 3: Verify build passes** 155 + 156 + Run: `npm run build` 157 + Expected: Build succeeds 158 + 159 + **Step 4: Commit** 160 + 161 + ```bash 162 + git add src/services/auth.js 163 + git commit -m "feat: navigate to return URL after OAuth callback" 164 + ``` 165 + 166 + --- 167 + 168 + ### Task 5: Manual Testing Checklist 169 + 170 + **Step 1: Start dev server** 171 + 172 + Run: `npm run dev` 173 + 174 + **Step 2: Test login from timeline** 175 + 176 + 1. Navigate to `http://localhost:5173/` 177 + 2. Click login, enter handle 178 + 3. Complete OAuth flow 179 + 4. Verify you return to `/` 180 + 181 + **Step 3: Test login from profile page** 182 + 183 + 1. Navigate to `http://localhost:5173/profile/grain.social` 184 + 2. Click login, enter handle 185 + 3. Complete OAuth flow 186 + 4. Verify you return to `/profile/grain.social` 187 + 188 + **Step 4: Test login from explore page** 189 + 190 + 1. Navigate to `http://localhost:5173/explore` 191 + 2. Click login, enter handle 192 + 3. Complete OAuth flow 193 + 4. Verify you return to `/explore` 194 + 195 + **Step 5: Test direct visit to callback** 196 + 197 + 1. Navigate directly to `http://localhost:5173/oauth/callback` 198 + 2. Verify it shows "Signing in..." briefly then redirects to `/` 199 + 200 + --- 201 + 202 + ### Task 6: Final Build Verification 203 + 204 + **Step 1: Run production build** 205 + 206 + Run: `npm run build` 207 + Expected: Build succeeds with no errors 208 + 209 + **Step 2: Commit any remaining changes** 210 + 211 + If all tests pass and no changes needed, this task is complete.