Highly ambitious ATProtocol AppView service and sdks

actually fix all deno lint and check errors

+119 -442
+2
deno.lock
··· 3 3 "specifiers": { 4 4 "jsr:@shikijs/shiki@*": "3.7.0", 5 5 "jsr:@slices/client@~0.1.0-alpha.3": "0.1.0-alpha.3", 6 + "jsr:@std/assert@*": "1.0.14", 6 7 "jsr:@std/assert@^1.0.14": "1.0.14", 7 8 "jsr:@std/cli@^1.0.21": "1.0.22", 8 9 "jsr:@std/cli@^1.0.22": "1.0.22", ··· 42 43 "dependencies": [ 43 44 "npm:@shikijs/core", 44 45 "npm:@shikijs/engine-oniguruma", 46 + "npm:@shikijs/types", 45 47 "npm:shiki" 46 48 ] 47 49 },
-390
frontend/src/client.test.ts
··· 1 - /** 2 - * OAuth Token Refresh Test 3 - * 4 - * Tests various OAuth token scenarios to understand what happens 5 - * when tokens expire and need to be refreshed. 6 - * 7 - * Run with: deno test client.test.ts --allow-all 8 - */ 9 - 10 - import { assertRejects } from "@std/assert"; 11 - import { AtProtoClient } from "./client.ts"; 12 - 13 - // Store original fetch to restore later 14 - const originalFetch = globalThis.fetch; 15 - 16 - // Mock OAuth client to simulate different token states 17 - class MockOAuthClient { 18 - private tokenState: "valid" | "expired" | "refresh_fails" | "no_tokens"; 19 - private callCount = 0; 20 - 21 - constructor( 22 - initialState: "valid" | "expired" | "refresh_fails" | "no_tokens", 23 - ) { 24 - this.tokenState = initialState; 25 - } 26 - 27 - ensureValidToken() { 28 - this.callCount++; 29 - console.log( 30 - `MockOAuth.ensureValidToken() called (attempt ${this.callCount})`, 31 - ); 32 - 33 - switch (this.tokenState) { 34 - case "valid": 35 - console.log("Returning valid tokens"); 36 - return Promise.resolve({ 37 - accessToken: "valid_access_token_12345", 38 - tokenType: "Bearer", 39 - }); 40 - 41 - case "expired": 42 - if (this.callCount === 1) { 43 - console.log("First call - tokens expired, attempting refresh..."); 44 - // Simulate successful refresh on first call 45 - this.tokenState = "valid"; 46 - return Promise.resolve({ 47 - accessToken: "refreshed_access_token_67890", 48 - tokenType: "Bearer", 49 - }); 50 - } else { 51 - console.log("Subsequent call - returning refreshed tokens"); 52 - return Promise.resolve({ 53 - accessToken: "refreshed_access_token_67890", 54 - tokenType: "Bearer", 55 - }); 56 - } 57 - 58 - case "refresh_fails": 59 - console.log("Token refresh failed"); 60 - throw new Error("OAuth token refresh failed: invalid_grant"); 61 - 62 - case "no_tokens": 63 - console.log("No tokens available"); 64 - throw new Error("No OAuth tokens found"); 65 - 66 - default: 67 - throw new Error("Unknown token state"); 68 - } 69 - } 70 - 71 - isAuthenticated() { 72 - return Promise.resolve(this.tokenState === "valid"); 73 - } 74 - } 75 - 76 - // Mock fetch function that simulates API responses 77 - function createMockFetch() { 78 - return ( 79 - url: string | URL | Request, 80 - init?: RequestInit, 81 - ): Promise<Response> => { 82 - const requestUrl = url.toString(); 83 - const method = init?.method || "GET"; 84 - const headers = (init?.headers as Record<string, string>) || {}; 85 - 86 - console.log(`Mock fetch: ${method} ${requestUrl}`); 87 - console.log(`Authorization header: ${headers["Authorization"] || "none"}`); 88 - 89 - // Check if request has valid authorization 90 - const hasAuth = headers["Authorization"]?.startsWith("Bearer valid") || 91 - headers["Authorization"]?.startsWith("Bearer refreshed"); 92 - 93 - if (method === "GET") { 94 - // GET requests always succeed (read-only operations) 95 - return new Response( 96 - JSON.stringify({ 97 - records: [ 98 - { 99 - uri: "test://uri", 100 - cid: "test-cid", 101 - value: { name: "Test Record" }, 102 - }, 103 - ], 104 - }), 105 - { 106 - status: 200, 107 - headers: { "Content-Type": "application/json" }, 108 - }, 109 - ); 110 - } else { 111 - // POST/PUT/DELETE require valid auth 112 - if (hasAuth) { 113 - return new Response( 114 - JSON.stringify({ 115 - uri: "test://created-uri", 116 - cid: "test-created-cid", 117 - }), 118 - { 119 - status: 200, 120 - headers: { "Content-Type": "application/json" }, 121 - }, 122 - ); 123 - } else { 124 - return new Response( 125 - JSON.stringify({ 126 - error: "Unauthorized", 127 - }), 128 - { 129 - status: 401, 130 - headers: { "Content-Type": "application/json" }, 131 - }, 132 - ); 133 - } 134 - } 135 - }; 136 - } 137 - 138 - function createTestClient( 139 - tokenState: "valid" | "expired" | "refresh_fails" | "no_tokens", 140 - ) { 141 - const mockOAuth = new MockOAuthClient(tokenState); 142 - return new AtProtoClient( 143 - "https://test-api.example.com", 144 - "at://did:plc:test/network.slices.slice/test", 145 - mockOAuth as unknown as Parameters<typeof AtProtoClient>[2], 146 - ); 147 - } 148 - 149 - Deno.test("Valid tokens - read operation should succeed", async () => { 150 - // Setup mock fetch 151 - globalThis.fetch = createMockFetch(); 152 - 153 - try { 154 - const client = createTestClient("valid"); 155 - await client.network.slices.slice.getRecords(); 156 - console.log("Read operation succeeded as expected"); 157 - } catch (error) { 158 - throw new Error(`Read operation should have succeeded: ${error}`); 159 - } finally { 160 - // Restore original fetch 161 - globalThis.fetch = originalFetch; 162 - } 163 - }); 164 - 165 - Deno.test("Valid tokens - write operation should succeed", async () => { 166 - // Setup mock fetch 167 - globalThis.fetch = createMockFetch(); 168 - 169 - try { 170 - const client = createTestClient("valid"); 171 - await client.network.slices.slice.updateRecord("test", { 172 - name: "Test Slice", 173 - domain: "network.slices", 174 - createdAt: new Date().toISOString(), 175 - }); 176 - console.log("Write operation succeeded as expected"); 177 - } catch (error) { 178 - throw new Error(`Write operation should have succeeded: ${error}`); 179 - } finally { 180 - // Restore original fetch 181 - globalThis.fetch = originalFetch; 182 - } 183 - }); 184 - 185 - Deno.test( 186 - "Expired tokens (refreshable) - read operation should succeed", 187 - async () => { 188 - // Setup mock fetch 189 - globalThis.fetch = createMockFetch(); 190 - 191 - try { 192 - const client = createTestClient("expired"); 193 - await client.network.slices.slice.getRecords(); 194 - console.log("Read operation succeeded after token refresh"); 195 - } catch (error) { 196 - throw new Error( 197 - `Read operation should have succeeded after refresh: ${error}`, 198 - ); 199 - } finally { 200 - // Restore original fetch 201 - globalThis.fetch = originalFetch; 202 - } 203 - }, 204 - ); 205 - 206 - Deno.test( 207 - "Expired tokens (refreshable) - write operation should succeed", 208 - async () => { 209 - // Setup mock fetch 210 - globalThis.fetch = createMockFetch(); 211 - 212 - try { 213 - const client = createTestClient("expired"); 214 - await client.network.slices.slice.updateRecord("test", { 215 - name: "Test Slice", 216 - domain: "network.slices", 217 - createdAt: new Date().toISOString(), 218 - }); 219 - console.log("Write operation succeeded after token refresh"); 220 - } catch (error) { 221 - throw new Error( 222 - `Write operation should have succeeded after refresh: ${error}`, 223 - ); 224 - } finally { 225 - // Restore original fetch 226 - globalThis.fetch = originalFetch; 227 - } 228 - }, 229 - ); 230 - 231 - Deno.test("Token refresh fails - read operation should succeed", async () => { 232 - // Setup mock fetch 233 - globalThis.fetch = createMockFetch(); 234 - 235 - try { 236 - const client = createTestClient("refresh_fails"); 237 - await client.network.slices.slice.getRecords(); 238 - console.log("Read operation succeeded without auth (as expected)"); 239 - } catch (error) { 240 - throw new Error( 241 - `Read operation should succeed even without auth: ${error}`, 242 - ); 243 - } finally { 244 - // Restore original fetch 245 - globalThis.fetch = originalFetch; 246 - } 247 - }); 248 - 249 - Deno.test( 250 - "Token refresh fails - write operation should fail with auth error", 251 - async () => { 252 - // Setup mock fetch 253 - globalThis.fetch = createMockFetch(); 254 - 255 - try { 256 - const client = createTestClient("refresh_fails"); 257 - await assertRejects( 258 - async () => { 259 - await client.network.slices.slice.updateRecord("test", { 260 - name: "Test Slice", 261 - domain: "network.slices", 262 - createdAt: new Date().toISOString(), 263 - }); 264 - }, 265 - Error, 266 - "Authentication required", 267 - "Write operation should fail with authentication error when tokens can't be refreshed", 268 - ); 269 - } finally { 270 - // Restore original fetch 271 - globalThis.fetch = originalFetch; 272 - } 273 - }, 274 - ); 275 - 276 - Deno.test("No tokens - read operation should succeed", async () => { 277 - // Setup mock fetch 278 - globalThis.fetch = createMockFetch(); 279 - 280 - try { 281 - const client = createTestClient("no_tokens"); 282 - await client.network.slices.slice.getRecords(); 283 - console.log("Read operation succeeded without tokens (as expected)"); 284 - } catch (error) { 285 - throw new Error( 286 - `Read operation should succeed even without tokens: ${error}`, 287 - ); 288 - } finally { 289 - // Restore original fetch 290 - globalThis.fetch = originalFetch; 291 - } 292 - }); 293 - 294 - Deno.test( 295 - "No tokens - write operation should fail with auth error", 296 - async () => { 297 - // Setup mock fetch 298 - globalThis.fetch = createMockFetch(); 299 - 300 - try { 301 - const client = createTestClient("no_tokens"); 302 - await assertRejects( 303 - async () => { 304 - await client.network.slices.slice.updateRecord("test", { 305 - name: "Test Slice", 306 - domain: "network.slices", 307 - createdAt: new Date().toISOString(), 308 - }); 309 - }, 310 - Error, 311 - "Authentication required", 312 - "Write operation should fail with authentication error when no tokens available", 313 - ); 314 - } finally { 315 - // Restore original fetch 316 - globalThis.fetch = originalFetch; 317 - } 318 - }, 319 - ); 320 - 321 - Deno.test("401 response triggers token refresh and retry", async () => { 322 - // Create a fetch that returns 401 first time, 200 second time 323 - let callCount = 0; 324 - const mockFetch = ( 325 - url: string | URL | Request, 326 - init?: RequestInit, 327 - ): Promise<Response> => { 328 - callCount++; 329 - const requestUrl = url.toString(); 330 - const method = init?.method || "GET"; 331 - const headers = (init?.headers as Record<string, string>) || {}; 332 - 333 - console.log(`Mock fetch call ${callCount}: ${method} ${requestUrl}`); 334 - console.log(`Authorization header: ${headers["Authorization"] || "none"}`); 335 - 336 - if (method === "POST") { 337 - if (callCount === 1) { 338 - // First call returns 401 339 - console.log("First call - returning 401 Unauthorized"); 340 - return Promise.resolve( 341 - new Response(JSON.stringify({ error: "Unauthorized" }), { 342 - status: 401, 343 - headers: { "Content-Type": "application/json" }, 344 - }), 345 - ); 346 - } else { 347 - // Second call should succeed (with refreshed token) 348 - console.log("Second call - returning success"); 349 - return Promise.resolve( 350 - new Response( 351 - JSON.stringify({ 352 - uri: "test://created-uri", 353 - cid: "test-created-cid", 354 - }), 355 - { 356 - status: 200, 357 - headers: { "Content-Type": "application/json" }, 358 - }, 359 - ), 360 - ); 361 - } 362 - } 363 - 364 - return Promise.resolve(new Response("Not found", { status: 404 })); 365 - }; 366 - 367 - globalThis.fetch = mockFetch; 368 - 369 - try { 370 - const client = createTestClient("valid"); 371 - await client.network.slices.slice.updateRecord("test", { 372 - name: "Test Slice", 373 - domain: "network.slices", 374 - createdAt: new Date().toISOString(), 375 - }); 376 - 377 - // Should have made exactly 2 calls 378 - console.log(`Total fetch calls made: ${callCount}`); 379 - if (callCount !== 2) { 380 - throw new Error(`Expected 2 fetch calls but got ${callCount}`); 381 - } 382 - 383 - console.log( 384 - "401 retry test passed - request was retried after token refresh", 385 - ); 386 - } finally { 387 - // Restore original fetch 388 - globalThis.fetch = originalFetch; 389 - } 390 - });
+8 -3
frontend/src/client.ts
··· 1 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-24 17:45:47 UTC 2 + // Generated at: 2025-09-24 17:50:48 UTC 3 3 // Lexicons: 25 4 4 5 5 /** ··· 50 50 */ 51 51 52 52 import { 53 + type AuthProvider, 53 54 type BlobRef, 54 55 type CountRecordsResponse, 55 56 type GetActorsParams, ··· 2419 2420 export class AtProtoClient extends SlicesClient { 2420 2421 readonly app: AppClient; 2421 2422 readonly network: NetworkClient; 2422 - readonly oauth?: OAuthClient; 2423 + readonly oauth?: OAuthClient | AuthProvider; 2423 2424 2424 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 2425 + constructor( 2426 + baseUrl: string, 2427 + sliceUri: string, 2428 + oauthClient?: OAuthClient | AuthProvider, 2429 + ) { 2425 2430 super(baseUrl, sliceUri, oauthClient); 2426 2431 this.app = new AppClient(this); 2427 2432 this.network = new NetworkClient(this);
+1
frontend/src/features/dashboard/templates/fragments/CreateSliceDialog.tsx
··· 62 62 <Button 63 63 type="button" 64 64 variant="secondary" 65 + /* @ts-ignore - Hyperscript attribute */ 65 66 _="on click remove #create-slice-modal" 66 67 > 67 68 Cancel
+1
frontend/src/features/docs/templates/DocsPage.tsx
··· 34 34 id="docs-nav" 35 35 className="block w-full px-3 py-2 text-base border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent" 36 36 value={currentSlug} 37 + /* @ts-ignore - Hyperscript attribute */ 37 38 _="on change set window.location to `/docs/${me.value}`" 38 39 > 39 40 {docs.map((doc) => (
+1
frontend/src/features/landing/templates/fragments/WaitlistFormModal.tsx
··· 28 28 <div class="flex justify-end space-x-3"> 29 29 <button 30 30 type="button" 31 + /* @ts-ignore - Hyperscript attribute */ 31 32 _="on click call #waitlist-modal.close()" 32 33 class="font-mono text-sm px-4 py-2 text-gray-700 hover:text-gray-900" 33 34 >
+2
frontend/src/features/landing/templates/fragments/WaitlistSuccessModal.tsx
··· 12 12 <dialog 13 13 id="waitlist-success-modal" 14 14 class="p-0 rounded-lg shadow-xl max-w-md m-auto backdrop:bg-black/50" 15 + /* @ts-ignore - Hyperscript attribute */ 15 16 _={show ? "on load call me.showModal()" : ""} 16 17 > 17 18 <div class="p-6"> ··· 29 30 </p> 30 31 <button 31 32 type="button" 33 + /* @ts-ignore - Hyperscript attribute */ 32 34 _="on click call #waitlist-success-modal.close() then call window.history.replaceState({}, document.title, '/')" 33 35 class="font-mono text-sm bg-gray-800 text-white px-6 py-2 hover:bg-gray-700 transition-colors" 34 36 >
+17 -17
frontend/src/features/settings/handlers.tsx
··· 4 4 import { requireAuth, withAuth } from "../../routes/middleware.ts"; 5 5 import { createSessionClient, publicClient } from "../../config.ts"; 6 6 import { buildAtUri } from "../../utils/at-uri.ts"; 7 - import type { SocialSlicesActorProfile } from "../../client.ts"; 8 7 import { SettingsPage } from "./templates/SettingsPage.tsx"; 9 8 import { recordBlobToCdnUrl } from "@slices/client"; 9 + import { NetworkSlicesActorProfile } from "../../client.ts"; 10 10 11 11 async function handleSettingsPage(req: Request): Promise<Response> { 12 12 const context = await withAuth(req); ··· 21 21 22 22 let profile: 23 23 | { 24 - displayName?: string; 25 - description?: string; 26 - avatar?: string; 27 - } 24 + displayName?: string; 25 + description?: string; 26 + avatar?: string; 27 + } 28 28 | undefined; 29 29 30 30 try { 31 - const profileRecord = await publicClient.network.slices.actor.profile 32 - .getRecord({ 31 + const profileRecord = 32 + await publicClient.network.slices.actor.profile.getRecord({ 33 33 uri: buildAtUri({ 34 34 did: context.currentUser.sub!, 35 35 collection: "network.slices.actor.profile", ··· 42 42 description: profileRecord.value.description, 43 43 avatar: profileRecord.value.avatar 44 44 ? recordBlobToCdnUrl( 45 - profileRecord, 46 - profileRecord.value.avatar, 47 - "avatar", 48 - ) 45 + profileRecord, 46 + profileRecord.value.avatar, 47 + "avatar" 48 + ) 49 49 : undefined, 50 50 }; 51 51 } ··· 59 59 currentUser={context.currentUser} 60 60 updated={updated === "true"} 61 61 error={error} 62 - />, 62 + /> 63 63 ); 64 64 } 65 65 ··· 74 74 const description = formData.get("description") as string; 75 75 const avatarFile = formData.get("avatar") as File; 76 76 77 - const profileData: Partial<SocialSlicesActorProfile> = { 77 + const profileData: Partial<NetworkSlicesActorProfile> = { 78 78 displayName: displayName?.trim() || undefined, 79 79 description: description?.trim() || undefined, 80 80 createdAt: new Date().toISOString(), ··· 103 103 throw new Error("User DID (sub) is required for profile operations"); 104 104 } 105 105 106 - const existingProfile = await publicClient.network.slices.actor.profile 107 - .getRecord({ 106 + const existingProfile = 107 + await publicClient.network.slices.actor.profile.getRecord({ 108 108 uri: buildAtUri({ 109 109 did: context.currentUser.sub, 110 110 collection: "network.slices.actor.profile", ··· 126 126 127 127 await sessionClient.network.slices.actor.profile.updateRecord( 128 128 "self", 129 - updatedProfile, 129 + updatedProfile 130 130 ); 131 131 } else { 132 132 await sessionClient.network.slices.actor.profile.createRecord( 133 133 profileData, 134 - true, 134 + true 135 135 ); 136 136 } 137 137
+1
frontend/src/features/settings/templates/fragments/SettingsResult.tsx
··· 26 26 <button 27 27 type="button" 28 28 className="mt-2 text-sm underline hover:no-underline" 29 + /* @ts-ignore - Hyperscript attribute */ 29 30 _="on click call window.location.reload()" 30 31 > 31 32 Refresh page to see changes
+1
frontend/src/features/slices/codegen/templates/SliceCodegenPage.tsx
··· 52 52 <Button 53 53 variant="success" 54 54 size="md" 55 + /* @ts-ignore - Hyperscript attribute */ 55 56 _={`on click js navigator.clipboard.writeText(${JSON.stringify( 56 57 generatedCode 57 58 )}).then(() => { me.textContent = 'Copied!'; setTimeout(() => me.textContent = 'Copy to Clipboard', 2000); }) end`}
+2
frontend/src/features/slices/lexicon/templates/LexiconDetailPage.tsx
··· 80 80 <div className="flex-shrink-0"> 81 81 <Button 82 82 variant="secondary" 83 + /* @ts-ignore - Hyperscript attribute */ 83 84 _={`on click call navigator.clipboard.writeText(${JSON.stringify( 84 85 lexiconJson 85 86 )})`} ··· 124 125 <input 125 126 type="checkbox" 126 127 checked={excludedFromSync === true} 128 + /* @ts-ignore - Hyperscript attribute */ 127 129 _="on change trigger submit on closest <form/>" 128 130 className="sr-only peer" 129 131 />
+1
frontend/src/features/slices/lexicon/templates/fragments/LexiconFormModal.tsx
··· 63 63 <Button 64 64 type="button" 65 65 variant="secondary" 66 + /* @ts-ignore - Hyperscript attribute */ 66 67 _="on click set #modal-container's innerHTML to ''" 67 68 > 68 69 Cancel
+1
frontend/src/features/slices/lexicon/templates/fragments/LexiconsList.tsx
··· 54 54 id="select-all" 55 55 className="accent-blue-600 dark:accent-white" 56 56 style="color-scheme: light dark;" 57 + /* @ts-ignore - Hyperscript attribute */ 57 58 _="on change 58 59 set checkboxes to document.querySelectorAll('input[name=lexicon_rkey]') 59 60 for cb in checkboxes
+2
frontend/src/features/slices/oauth/handlers.tsx
··· 45 45 </p> 46 46 <button 47 47 type="button" 48 + /* @ts-ignore - Hyperscript attribute */ 48 49 _="on click set #modal-container's innerHTML to ''" 49 50 className="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition" 50 51 > ··· 212 213 </p> 213 214 <button 214 215 type="button" 216 + /* @ts-ignore - Hyperscript attribute */ 215 217 _="on click set #modal-container's innerHTML to ''" 216 218 className="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition" 217 219 >
+2
frontend/src/features/slices/oauth/templates/fragments/OAuthClientModal.tsx
··· 121 121 <Button 122 122 type="button" 123 123 variant="secondary" 124 + /* @ts-ignore - Hyperscript attribute */ 124 125 _="on click set #modal-container's innerHTML to ''" 125 126 > 126 127 Cancel ··· 212 213 <Button 213 214 type="button" 214 215 variant="secondary" 216 + /* @ts-ignore - Hyperscript attribute */ 215 217 _="on click set #modal-container's innerHTML to ''" 216 218 > 217 219 Cancel
+1
frontend/src/features/slices/oauth/templates/fragments/OAuthRegistrationResult.tsx
··· 62 62 <Button 63 63 type="button" 64 64 variant="secondary" 65 + /* @ts-ignore - Hyperscript attribute */ 65 66 _="on click set #modal-container's innerHTML to ''" 66 67 > 67 68 Close
+17 -9
frontend/src/features/slices/records/handlers.tsx
··· 8 8 withSliceAccess, 9 9 } from "../../../routes/slice-middleware.ts"; 10 10 import { extractSliceParams } from "../../../utils/slice-params.ts"; 11 - import type { IndexedRecord } from "../../../client.ts"; 11 + import type { IndexedRecord } from "@slices/client"; 12 12 import { RecordsList } from "./templates/fragments/RecordsList.tsx"; 13 13 import { Card } from "../../../shared/fragments/Card.tsx"; 14 14 import { EmptyState } from "../../../shared/fragments/EmptyState.tsx"; ··· 17 17 18 18 async function handleSliceRecordsPage( 19 19 req: Request, 20 - params?: URLPatternResult, 20 + params?: URLPatternResult 21 21 ): Promise<Response> { 22 22 const authContext = await withAuth(req); 23 23 const sliceParams = extractSliceParams(params); ··· 29 29 const context = await withSliceAccess( 30 30 authContext, 31 31 sliceParams.handle, 32 - sliceParams.sliceId, 32 + sliceParams.sliceId 33 33 ); 34 34 35 35 // Check if slice exists (records page is public) ··· 52 52 // Fetch real records if a collection is selected 53 53 let records: Array<IndexedRecord & { pretty_value: string }> = []; 54 54 55 - if ((selectedCollection || (searchQuery && searchQuery.trim() !== "")) && collections.length > 0) { 55 + if ( 56 + (selectedCollection || (searchQuery && searchQuery.trim() !== "")) && 57 + collections.length > 0 58 + ) { 56 59 try { 57 - const sliceClient = getSliceClient(authContext, sliceParams.sliceId, context.sliceContext.profileDid); 58 - const recordsResult = await sliceClient.network.slices.slice 59 - .getSliceRecords({ 60 + const sliceClient = getSliceClient( 61 + authContext, 62 + sliceParams.sliceId, 63 + context.sliceContext.profileDid 64 + ); 65 + const recordsResult = 66 + await sliceClient.network.slices.slice.getSliceRecords({ 60 67 where: { 61 68 ...(selectedCollection && { 62 69 collection: { eq: selectedCollection }, 63 70 }), 64 - ...(searchQuery && searchQuery.trim() !== "" && { json: { contains: searchQuery } }), 71 + ...(searchQuery && 72 + searchQuery.trim() !== "" && { json: { contains: searchQuery } }), 65 73 ...(selectedAuthor && { did: { eq: selectedAuthor } }), 66 74 }, 67 75 limit: 20, ··· 135 143 availableCollections={collections} 136 144 currentUser={authContext.currentUser} 137 145 hasSliceAccess={context.sliceContext?.hasAccess} 138 - />, 146 + /> 139 147 ); 140 148 } 141 149
+2
frontend/src/features/slices/sync/templates/fragments/SyncFormModal.tsx
··· 71 71 <Button 72 72 type="button" 73 73 variant="secondary" 74 + /* @ts-ignore - Hyperscript attribute */ 74 75 _="on click set #modal-container's innerHTML to ''" 75 76 > 76 77 Cancel ··· 83 84 <i 84 85 data-lucide="loader-2" 85 86 className="htmx-indicator animate-spin mr-2 h-4 w-4" 87 + /* @ts-ignore - Hyperscript attribute */ 86 88 _="on load js lucide.createIcons() end" 87 89 > 88 90 </i>
+1
frontend/src/features/slices/waitlist/templates/fragments/CreateInviteModal.tsx
··· 59 59 <Button 60 60 type="button" 61 61 variant="outline" 62 + /* @ts-ignore - Hyperscript attribute */ 62 63 _="on click set #modal-container's innerHTML to ''" 63 64 > 64 65 Cancel
+1
frontend/src/shared/fragments/AvatarInput.tsx
··· 67 67 name="avatar" 68 68 accept="image/*" 69 69 className="hidden" 70 + /* @ts-ignore - Hyperscript attribute */ 70 71 _="on change 71 72 if my.files.length > 0 72 73 set file to my.files[0]
+14 -8
frontend/src/shared/fragments/Button.tsx
··· 1 1 import type { JSX } from "preact"; 2 2 import { cn } from "../../utils/cn.ts"; 3 + import { ComponentChildren } from "preact"; 3 4 4 5 type ButtonVariant = 5 6 | "primary" // zinc-900 background ··· 12 13 type ButtonSize = "sm" | "md" | "lg"; 13 14 14 15 export interface ButtonProps 15 - extends JSX.ButtonHTMLAttributes<HTMLButtonElement> { 16 + extends Omit<JSX.IntrinsicElements["button"], "size"> { 16 17 variant?: ButtonVariant; 17 18 size?: ButtonSize; 18 - children: JSX.Element | JSX.Element[] | string; 19 + children: ComponentChildren; 19 20 href?: string; // If provided, renders as a link styled like a button 20 21 } 21 22 22 23 const variantClasses = { 23 - primary: "bg-zinc-50 hover:bg-zinc-50/90 dark:bg-zinc-600 dark:hover:bg-zinc-600/90 text-zinc-900 dark:text-zinc-100 border border-zinc-200 dark:border-zinc-500/70", 24 - secondary: "bg-zinc-100 hover:bg-zinc-100/90 dark:bg-zinc-700 dark:hover:bg-zinc-700/90 text-zinc-900 dark:text-zinc-100 border border-zinc-200/70 dark:border-zinc-600/70", 25 - outline: "bg-transparent border border-zinc-300/70 hover:bg-zinc-50 dark:border-zinc-600/70 dark:hover:bg-zinc-600/10 text-zinc-900 dark:text-zinc-100", 26 - success: "bg-green-600 hover:bg-green-600/90 dark:bg-green-600 dark:hover:bg-green-600/90 text-white border border-green-700 dark:border-green-500", 27 - danger: "bg-red-600 hover:bg-red-600/90 dark:bg-red-600 dark:hover:bg-red-600/90 text-white border border-red-700 dark:border-red-500", 24 + primary: 25 + "bg-zinc-50 hover:bg-zinc-50/90 dark:bg-zinc-600 dark:hover:bg-zinc-600/90 text-zinc-900 dark:text-zinc-100 border border-zinc-200 dark:border-zinc-500/70", 26 + secondary: 27 + "bg-zinc-100 hover:bg-zinc-100/90 dark:bg-zinc-700 dark:hover:bg-zinc-700/90 text-zinc-900 dark:text-zinc-100 border border-zinc-200/70 dark:border-zinc-600/70", 28 + outline: 29 + "bg-transparent border border-zinc-300/70 hover:bg-zinc-50 dark:border-zinc-600/70 dark:hover:bg-zinc-600/10 text-zinc-900 dark:text-zinc-100", 30 + success: 31 + "bg-green-600 hover:bg-green-600/90 dark:bg-green-600 dark:hover:bg-green-600/90 text-white border border-green-700 dark:border-green-500", 32 + danger: 33 + "bg-red-600 hover:bg-red-600/90 dark:bg-red-600 dark:hover:bg-red-600/90 text-white border border-red-700 dark:border-red-500", 28 34 blue: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white border border-blue-700 dark:border-blue-500", 29 35 }; 30 36 ··· 48 54 "rounded transition-colors inline-flex items-center font-medium", 49 55 variantClasses[variant], 50 56 sizeClasses[size], 51 - className, 57 + className 52 58 ); 53 59 54 60 if (href) {
+1 -1
frontend/src/shared/fragments/Input.tsx
··· 3 3 4 4 type InputSize = "sm" | "md" | "lg"; 5 5 6 - export interface InputProps extends JSX.InputHTMLAttributes<HTMLInputElement> { 6 + export interface InputProps extends Omit<JSX.IntrinsicElements['input'], 'size'> { 7 7 label?: string; 8 8 error?: string; 9 9 size?: InputSize;
+1
frontend/src/shared/fragments/LogViewer.tsx
··· 66 66 <details className="mt-2"> 67 67 <summary 68 68 className="cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-300" 69 + /* @ts-ignore - Hyperscript attribute */ 69 70 _="on click toggle .hidden on next <pre/>" 70 71 > 71 72 <Text as="span" size="xs" variant="muted">View metadata</Text>
+2
frontend/src/shared/fragments/Modal.tsx
··· 29 29 return ( 30 30 <div 31 31 className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" 32 + /* @ts-ignore - Hyperscript attribute */ 32 33 _={`on click if event.target === me then ${ 33 34 onClose.replace("on click ", "") 34 35 }`} ··· 41 42 </div> 42 43 <button 43 44 type="button" 45 + /* @ts-ignore - Hyperscript attribute */ 44 46 _={onClose} 45 47 className="text-gray-400 dark:text-zinc-500 hover:text-gray-600 dark:hover:text-zinc-400 text-2xl leading-none" 46 48 >
+1
frontend/src/shared/fragments/Navigation.tsx
··· 41 41 <button 42 42 type="button" 43 43 className="flex items-center p-1 rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" 44 + /* @ts-ignore - Hyperscript attribute */ 44 45 _="on click toggle .hidden on #avatar-dropdown 45 46 on click from document 46 47 if not me.contains(event.target) and not #avatar-dropdown.contains(event.target)
+5 -3
frontend/src/shared/fragments/PageHeader.tsx
··· 1 - import type { JSX } from "preact"; 1 + import { ComponentChildren } from "preact"; 2 2 3 3 interface PageHeaderProps { 4 4 title: string; 5 - children?: JSX.Element | JSX.Element[]; 5 + children?: ComponentChildren; 6 6 } 7 7 8 8 export function PageHeader({ title, children }: PageHeaderProps) { 9 9 return ( 10 10 <div className="flex items-center justify-between mb-8"> 11 - <h1 className="text-3xl font-bold text-zinc-900 dark:text-white">{title}</h1> 11 + <h1 className="text-3xl font-bold text-zinc-900 dark:text-white"> 12 + {title} 13 + </h1> 12 14 {children && <div className="flex items-center gap-4">{children}</div>} 13 15 </div> 14 16 );
+1 -1
frontend/src/shared/fragments/Select.tsx
··· 6 6 type SelectSize = "sm" | "md" | "lg"; 7 7 8 8 export interface SelectProps 9 - extends JSX.SelectHTMLAttributes<HTMLSelectElement> { 9 + extends Omit<JSX.IntrinsicElements['select'], 'size'> { 10 10 label?: string; 11 11 error?: string; 12 12 size?: SelectSize;
+17 -1
frontend/src/shared/fragments/Text.tsx
··· 13 13 14 14 type TextSize = "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl"; 15 15 16 + type TextWeight = "normal" | "medium" | "semibold" | "bold"; 17 + 16 18 type TextElement = 17 19 | "p" 18 20 | "span" ··· 31 33 interface TextProps { 32 34 variant?: TextVariant; 33 35 size?: TextSize; 36 + weight?: TextWeight; 34 37 as?: TextElement; 35 38 className?: string; 36 39 children: ComponentChildren; ··· 57 60 "3xl": "text-3xl", 58 61 }; 59 62 63 + const textWeights = { 64 + normal: "font-normal", 65 + medium: "font-medium", 66 + semibold: "font-semibold", 67 + bold: "font-bold", 68 + }; 69 + 60 70 export function Text({ 61 71 variant = "primary", 62 72 size = "base", 73 + weight, 63 74 as = "span", 64 75 className, 65 76 children, 66 77 }: TextProps): JSX.Element { 67 78 const Component = as; 68 79 69 - const classes = cn(textColors[variant], textSizes[size], className); 80 + const classes = cn( 81 + textColors[variant], 82 + textSizes[size], 83 + weight && textWeights[weight], 84 + className 85 + ); 70 86 71 87 return ( 72 88 <Component className={classes}>
+1 -1
frontend/src/shared/fragments/Textarea.tsx
··· 4 4 type TextareaSize = "sm" | "md" | "lg"; 5 5 6 6 export interface TextareaProps 7 - extends JSX.TextareaHTMLAttributes<HTMLTextAreaElement> { 7 + extends Omit<JSX.IntrinsicElements['textarea'], 'size'> { 8 8 label?: string; 9 9 error?: string; 10 10 size?: TextareaSize;
+8 -3
packages/cli/src/generated_client.ts
··· 1 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-24 17:45:39 UTC 2 + // Generated at: 2025-09-24 17:50:27 UTC 3 3 // Lexicons: 25 4 4 5 5 /** ··· 50 50 */ 51 51 52 52 import { 53 + type AuthProvider, 53 54 type BlobRef, 54 55 type CountRecordsResponse, 55 56 type GetActorsParams, ··· 2419 2420 export class AtProtoClient extends SlicesClient { 2420 2421 readonly app: AppClient; 2421 2422 readonly network: NetworkClient; 2422 - readonly oauth?: OAuthClient; 2423 + readonly oauth?: OAuthClient | AuthProvider; 2423 2424 2424 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 2425 + constructor( 2426 + baseUrl: string, 2427 + sliceUri: string, 2428 + oauthClient?: OAuthClient | AuthProvider, 2429 + ) { 2425 2430 super(baseUrl, sliceUri, oauthClient); 2426 2431 this.app = new AppClient(this); 2427 2432 this.network = new NetworkClient(this);
+2 -2
packages/codegen/src/client.ts
··· 162 162 ? [ 163 163 { 164 164 name: "oauth", 165 - type: "OAuthClient", 165 + type: "OAuthClient | AuthProvider", 166 166 isReadonly: true, 167 167 hasQuestionToken: true, 168 168 }, ··· 188 188 { name: "sliceUri", type: "string" }, 189 189 { 190 190 name: "oauthClient", 191 - type: "OAuthClient", 191 + type: "OAuthClient | AuthProvider", 192 192 hasQuestionToken: true, 193 193 }, 194 194 ]
+2 -3
packages/codegen/src/mod.ts
··· 279 279 280 280 ${ 281 281 excludeSlicesClient 282 - ? 'import { SlicesClient, type RecordResponse, type GetRecordsResponse, type CountRecordsResponse, type GetRecordParams, type WhereCondition, type IndexedRecordFields, type SortField, type BlobRef } from "@slices/client";' 283 - : 'import { SlicesClient, type RecordResponse, type GetRecordsResponse, type CountRecordsResponse, type GetRecordParams, type WhereCondition, type IndexedRecordFields, type SortField, type GetActorsParams, type GetActorsResponse, type BlobRef, type SliceLevelRecordsParams, type SliceRecordsOutput } from "@slices/client";' 282 + ? 'import { SlicesClient, type RecordResponse, type GetRecordsResponse, type CountRecordsResponse, type GetRecordParams, type WhereCondition, type IndexedRecordFields, type SortField, type BlobRef, type AuthProvider } from "@slices/client";\nimport type { OAuthClient } from "@slices/oauth";' 283 + : 'import { SlicesClient, type RecordResponse, type GetRecordsResponse, type CountRecordsResponse, type GetRecordParams, type WhereCondition, type IndexedRecordFields, type SortField, type GetActorsParams, type GetActorsResponse, type BlobRef, type SliceLevelRecordsParams, type SliceRecordsOutput, type AuthProvider } from "@slices/client";\nimport type { OAuthClient } from "@slices/oauth";' 284 284 } 285 - import type { OAuthClient } from "@slices/oauth"; 286 285 287 286 `; 288 287 }