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