this repo has no description
1# Consent Page Profile Card Implementation Plan 2 3> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 5**Goal:** Show the authorizing user's Bluesky profile (avatar, name, handle) on the OAuth consent page. 6 7**Architecture:** Add inline HTML/CSS/JS to the consent page. Profile is fetched client-side from Bluesky's public API using the `login_hint` parameter. Graceful degradation if fetch fails. 8 9**Tech Stack:** Vanilla JS, Bluesky public API (`app.bsky.actor.getProfile`) 10 11--- 12 13### Task 1: Update renderConsentPage signature 14 15**Files:** 16- Modify: `src/pds.js:5008-5017` (function signature and JSDoc) 17 18**Step 1: Add loginHint to JSDoc and parameters** 19 20Change the function signature from: 21```javascript 22/** 23 * @param {{ clientName: string, clientId: string, scope: string, requestUri: string, error?: string }} params 24 * @returns {string} HTML page content 25 */ 26function renderConsentPage({ 27 clientName, 28 clientId, 29 scope, 30 requestUri, 31 error = '', 32}) { 33``` 34 35To: 36```javascript 37/** 38 * @param {{ clientName: string, clientId: string, scope: string, requestUri: string, loginHint?: string, error?: string }} params 39 * @returns {string} HTML page content 40 */ 41function renderConsentPage({ 42 clientName, 43 clientId, 44 scope, 45 requestUri, 46 loginHint = '', 47 error = '', 48}) { 49``` 50 51**Step 2: Verify syntax is correct** 52 53Run: `node --check src/pds.js` 54Expected: No output (success) 55 56--- 57 58### Task 2: Add profile card CSS 59 60**Files:** 61- Modify: `src/pds.js:5027-5055` (inside the `<style>` block) 62 63**Step 1: Add profile card styles after existing styles** 64 65Add before `</style></head>`: 66```css 67.profile-card{display:flex;align-items:center;gap:12px;padding:16px;background:#2a2a2a;border-radius:8px;margin-bottom:20px} 68.profile-card.loading .avatar{background:#404040;animation:pulse 1.5s infinite} 69.profile-card .avatar{width:48px;height:48px;border-radius:50%;background:#404040;flex-shrink:0} 70.profile-card .avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover} 71.profile-card .info{min-width:0} 72.profile-card .name{color:#fff;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 73.profile-card .handle{color:#808080;font-size:14px} 74@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}} 75``` 76 77**Step 2: Verify syntax is correct** 78 79Run: `node --check src/pds.js` 80Expected: No output (success) 81 82--- 83 84### Task 3: Add profile card HTML 85 86**Files:** 87- Modify: `src/pds.js:5056-5057` (after `<body>` opening, before `<h2>`) 88 89**Step 1: Add profile card HTML conditionally** 90 91Replace: 92```javascript 93<body><h2>Sign in to authorize</h2> 94``` 95 96With: 97```javascript 98<body> 99${loginHint ? `<div class="profile-card loading" id="profile-card"> 100<div class="avatar" id="profile-avatar"></div> 101<div class="info"><div class="name" id="profile-name">Loading...</div> 102<div class="handle" id="profile-handle">${escapeHtml(loginHint.startsWith('did:') ? loginHint : '@' + loginHint)}</div></div> 103</div>` : ''} 104<h2>Sign in to authorize</h2> 105``` 106 107**Step 2: Verify syntax is correct** 108 109Run: `node --check src/pds.js` 110Expected: No output (success) 111 112--- 113 114### Task 4: Add profile fetch script 115 116**Files:** 117- Modify: `src/pds.js:5066` (before `</body></html>`) 118 119**Step 1: Add inline script to fetch profile** 120 121Replace: 122```javascript 123</form></body></html>`; 124``` 125 126With: 127```javascript 128</form> 129${loginHint ? `<script> 130(async()=>{ 131const card=document.getElementById('profile-card'); 132if(!card)return; 133try{ 134const r=await fetch('https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor='+encodeURIComponent('${escapeHtml(loginHint)}')); 135if(!r.ok)throw new Error(); 136const p=await r.json(); 137document.getElementById('profile-avatar').innerHTML=p.avatar?'<img src="'+p.avatar+'" alt="">':''; 138document.getElementById('profile-name').textContent=p.displayName||p.handle; 139document.getElementById('profile-handle').textContent='@'+p.handle; 140card.classList.remove('loading'); 141}catch(e){card.classList.remove('loading')} 142})(); 143</script>` : ''} 144</body></html>`; 145``` 146 147**Step 2: Verify syntax is correct** 148 149Run: `node --check src/pds.js` 150Expected: No output (success) 151 152--- 153 154### Task 5: Pass loginHint from PAR flow 155 156**Files:** 157- Modify: `src/pds.js:3954-3959` (PAR flow renderConsentPage call) 158 159**Step 1: Add loginHint to renderConsentPage call** 160 161Change: 162```javascript 163 return new Response( 164 renderConsentPage({ 165 clientName: clientMetadata.client_name || clientId, 166 clientId: clientId || '', 167 scope: parameters.scope || 'atproto', 168 requestUri: requestUri || '', 169 }), 170``` 171 172To: 173```javascript 174 return new Response( 175 renderConsentPage({ 176 clientName: clientMetadata.client_name || clientId, 177 clientId: clientId || '', 178 scope: parameters.scope || 'atproto', 179 requestUri: requestUri || '', 180 loginHint: parameters.login_hint || '', 181 }), 182``` 183 184**Step 2: Verify syntax is correct** 185 186Run: `node --check src/pds.js` 187Expected: No output (success) 188 189--- 190 191### Task 6: Pass loginHint from direct flow 192 193**Files:** 194- Modify: `src/pds.js:4022-4027` (direct flow renderConsentPage call) 195 196**Step 1: Add loginHint to renderConsentPage call** 197 198Change: 199```javascript 200 return new Response( 201 renderConsentPage({ 202 clientName: clientMetadata.client_name || clientId, 203 clientId: clientId, 204 scope: scope || 'atproto', 205 requestUri: newRequestUri, 206 }), 207``` 208 209To: 210```javascript 211 return new Response( 212 renderConsentPage({ 213 clientName: clientMetadata.client_name || clientId, 214 clientId: clientId, 215 scope: scope || 'atproto', 216 requestUri: newRequestUri, 217 loginHint: loginHint || '', 218 }), 219``` 220 221**Step 2: Verify syntax is correct** 222 223Run: `node --check src/pds.js` 224Expected: No output (success) 225 226--- 227 228### Task 7: Run tests and commit 229 230**Step 1: Run full test suite** 231 232Run: `npm test` 233Expected: All 126 tests pass 234 235**Step 2: Commit changes** 236 237```bash 238git add src/pds.js docs/plans/2025-01-09-consent-profile-card.md 239git commit -m "feat: add profile card to OAuth consent page 240 241Shows the authorizing user's avatar, display name, and handle 242on the consent page. Fetches from Bluesky public API using 243the login_hint parameter. Degrades gracefully if fetch fails." 244``` 245 246--- 247 248## Manual Testing 249 250After implementation, test by: 251 2521. Start local PDS: `npx wrangler dev` 2532. Trigger OAuth flow with login_hint parameter 2543. Verify profile card shows on consent page 2554. Verify it degrades gracefully with invalid login_hint