add unblock functionality

- add xyz.fake.inbox.unblock endpoint to pds.js
- add unblockSender method to client
- toggle reject/unblock button based on blocked state
- fix panel heights with CSS

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

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

Changed files
+54 -4
src
lib
routes
+18
src/lib/client.js
··· 177 177 } 178 178 return false; 179 179 } 180 + 181 + async unblockSender(senderDid) { 182 + const res = await fetch(`${PDS_URL}/xrpc/xyz.fake.inbox.unblock`, { 183 + method: 'POST', 184 + headers: { 185 + 'Content-Type': 'application/json', 186 + Authorization: `Bearer ${this.accessToken}` 187 + }, 188 + body: JSON.stringify({ did: senderDid }) 189 + }); 190 + 191 + if (res.ok) { 192 + await this.syncState(); 193 + this.blocked.delete(senderDid); 194 + return true; 195 + } 196 + return false; 197 + } 180 198 } 181 199 182 200 export class LabelerClient {
+5 -1
src/lib/components/PdsPanel.svelte
··· 54 54 background: #111; 55 55 border: 1px solid #222; 56 56 padding: 1rem; 57 + height: 400px; 58 + display: flex; 59 + flex-direction: column; 57 60 } 58 61 59 62 h2 { ··· 115 118 background: #0a0a0a; 116 119 border: 1px solid #1a1a1a; 117 120 padding: 0.5rem 0.75rem; 118 - max-height: 160px; 121 + flex: 1; 119 122 overflow-y: auto; 123 + min-height: 0; 120 124 } 121 125 .inbox h3 { 122 126 font-size: 10px;
+31 -3
src/routes/+page.svelte
··· 104 104 } 105 105 } 106 106 107 + async function unblockSender() { 108 + try { 109 + if (await recipient.unblockSender(sender.did)) { 110 + log(`${recipientHandle} unblocked ${senderHandle}`, 'green'); 111 + } else { 112 + log(`${senderHandle} was not blocked`, 'dim'); 113 + } 114 + refresh(); 115 + } catch (e) { 116 + log(`error: ${e.message}`, 'red'); 117 + } 118 + } 119 + 107 120 function swap() { 108 121 [senderHandle, recipientHandle] = [recipientHandle, senderHandle]; 109 122 } ··· 185 198 <Tooltip text="recipient accepts sender's message request"> 186 199 <button class="accept" onclick={acceptRequest}>accept</button> 187 200 </Tooltip> 188 - <Tooltip text="reject request and block sender permanently"> 189 - <button class="reject" onclick={rejectRequest}>reject</button> 190 - </Tooltip> 201 + {#key $tick} 202 + {#if getPds(recipientHandle)?.blocked?.has(getPds(senderHandle)?.did)} 203 + <Tooltip text="unblock sender so they can message again"> 204 + <button class="unblock" onclick={unblockSender}>unblock</button> 205 + </Tooltip> 206 + {:else} 207 + <Tooltip text="reject request and block sender permanently"> 208 + <button class="reject" onclick={rejectRequest}>reject</button> 209 + </Tooltip> 210 + {/if} 211 + {/key} 191 212 <Tooltip text="labeler marks sender as spam (all PDSes reject)"> 192 213 <button class="spam" onclick={toggleSpam}>spam</button> 193 214 </Tooltip> ··· 365 386 } 366 387 button.reject:hover { 367 388 background: rgba(170, 68, 68, 0.1); 389 + } 390 + button.unblock { 391 + border-color: #2a4a6a; 392 + color: #6a9fd4; 393 + } 394 + button.unblock:hover { 395 + background: rgba(106, 159, 212, 0.1); 368 396 } 369 397 button.spam { 370 398 border-color: #4a4020;